From 7300515205f464a903291c794427560f1ad5a5ed Mon Sep 17 00:00:00 2001 From: Bernd Hufmann Date: Wed, 20 Jul 2011 11:56:58 -0400 Subject: [PATCH] Contribution for Bug352466: [TMF] Implement UML2 Sequence Diagram Framework --- .../lttng/tests/event/LttngTimestampTest.java | 6 +- .../META-INF/MANIFEST.MF | 4 +- org.eclipse.linuxtools.lttng/pom.xml | 2 +- .../lttng/event/LttngTimestamp.java | 25 +- .../META-INF/MANIFEST.MF | 1 + org.eclipse.linuxtools.tmf.tests/pom.xml | 20 +- .../linuxtools/tmf/tests/AllTmfCoreTests.java | 1 + .../tmf/tests/event/TmfTimestampTest.java | 74 + .../linuxtools/tmf/tests/uml2sd/AllTests.java | 20 + .../TmfAsyncSequenceDiagramEventTest.java | 119 ++ .../TmfSyncSequenceDiagramEventTest.java | 100 + .../tmf/trace/TmfEventParserStub.java | 76 +- .../linuxtools/tmf/trace/TmfTraceStub.java | 107 +- .../META-INF/MANIFEST.MF | 5 +- .../plugin.xml | 44 + org.eclipse.linuxtools.tmf.ui.tests/pom.xml | 20 +- .../tmf/ui/tests/AllTmfUITests.java | 17 + .../uml2sd/handlers/widgets/AllTests.java | 27 + .../uml2sd/handlers/widgets/CriteriaTest.java | 276 +++ .../ui/tests/views/uml2sd/impl/AllTests.java | 34 + .../uml2sd/impl/IUml2SDTestConstants.java | 39 + .../uml2sd/impl/IUml2SdSignalValidator.java | 45 + .../impl/TmfUml2SDSyncLoaderExpTest.java | 181 ++ .../impl/TmfUml2SDSyncLoaderFilterTest.java | 163 ++ .../impl/TmfUml2SDSyncLoaderFindTest.java | 292 +++ .../impl/TmfUml2SDSyncLoaderPagesTest.java | 215 ++ .../impl/TmfUml2SDSyncLoaderSignalTest.java | 121 ++ .../impl/TmfUml2SDSyncLoaderTimeTest.java | 275 +++ .../uml2sd/impl/Uml2SDSignalValidator.java | 163 ++ .../views/uml2sd/impl/Uml2SDTestFacility.java | 306 +++ .../uml2sd/impl/Uml2SDTestTimestamp.java | 33 + .../ui/tests/views/uml2sd/load/AllTests.java | 27 + .../views/uml2sd/load/LoadersManagerTest.java | 176 ++ .../tracesets/sdEvents | Bin 0 -> 5722500 bytes .../tmf/ui/tests/uml2sd/load/TestLoaders.java | 476 +++++ .../uml2sd/trace/TmfUml2SDTestTrace.java | 97 + org.eclipse.linuxtools.tmf.ui/.options | 7 + .../META-INF/MANIFEST.MF | 15 +- .../icons/elcl16/backward_nav.gif | Bin 0 -> 327 bytes .../icons/elcl16/forward_nav.gif | Bin 0 -> 327 bytes .../icons/elcl16/gotopage_menu.gif | Bin 0 -> 1024 bytes .../icons/elcl16/next_menu.gif | Bin 0 -> 542 bytes .../icons/elcl16/node_end.gif | Bin 0 -> 895 bytes .../icons/elcl16/node_start.gif | Bin 0 -> 873 bytes .../icons/elcl16/prev_menu.gif | Bin 0 -> 322 bytes .../icons/elcl16/search_seqdiag_menu.gif | Bin 0 -> 601 bytes .../icons/elcl16/select_menu.gif | Bin 0 -> 871 bytes .../icons/eview16/sequencediagram_view.gif | Bin 0 -> 225 bytes .../icons/eview16/statistics_view.gif | Bin 0 -> 343 bytes .../icons/obj16/arrow_colapse.bmp | Bin 0 -> 3078 bytes .../icons/obj16/arrow_up.bmp | Bin 0 -> 126 bytes .../icons/obj16/class.gif | Bin 0 -> 996 bytes .../icons/obj16/classobject_obj.gif | Bin 0 -> 586 bytes .../icons/obj16/collapsed_obj.gif | Bin 0 -> 595 bytes .../icons/obj16/node_obj.gif | Bin 0 -> 579 bytes .../icons/obj16/package_obj.gif | Bin 0 -> 574 bytes .../icons/obj16/plus_obj.gif | Bin 0 -> 871 bytes .../icons/obj16/process_obj.gif | Bin 0 -> 1027 bytes .../icons/obj16/thread_obj.gif | Bin 0 -> 538 bytes .../icons/obj16/zoom_mask.bmp | Bin 0 -> 190 bytes .../icons/obj16/zoomin_obj.bmp | Bin 0 -> 190 bytes .../icons/obj16/zoomout_obj.bmp | Bin 0 -> 190 bytes .../icons/ovr16/error_ovr.gif | Bin 0 -> 82 bytes .../icons/wizban/trace_import_wiz.png | Bin 5932 -> 0 bytes .../plugin.properties | 23 + org.eclipse.linuxtools.tmf.ui/plugin.xml | 132 ++ org.eclipse.linuxtools.tmf.ui/pom.xml | 2 +- ...ipse.linuxtools.tmf.ui.parserprovider.exsd | 204 +- ...clipse.linuxtools.tmf.ui.uml2SDLoader.exsd | 193 ++ .../linuxtools/tmf/ui/ITmfImageConstants.java | 44 + .../linuxtools/tmf/ui/TmfUiPlugin.java | 39 +- .../linuxtools/tmf/ui/TmfUiTracer.java | 164 ++ .../preferences/TmfTracingPreferencePage.java | 59 + .../tmf/ui/views/uml2sd/DiagramToolTip.java | 109 + .../tmf/ui/views/uml2sd/DrawableToolTip.java | 180 ++ .../uml2sd/ITimeCompressionListener.java | 26 + .../linuxtools/tmf/ui/views/uml2sd/NGC.java | 688 +++++++ .../tmf/ui/views/uml2sd/SDView.java | 1003 ++++++++++ .../tmf/ui/views/uml2sd/SDWidget.java | 1713 ++++++++++++++++ .../uml2sd/SDWidgetSelectionProvider.java | 89 + .../tmf/ui/views/uml2sd/ScrollView.java | 1757 +++++++++++++++++ .../ui/views/uml2sd/TimeCompressionBar.java | 920 +++++++++ .../ui/views/uml2sd/core/AsyncMessage.java | 414 ++++ .../views/uml2sd/core/AsyncMessageReturn.java | 74 + .../tmf/ui/views/uml2sd/core/BaseMessage.java | 660 +++++++ .../uml2sd/core/BasicExecutionOccurrence.java | 232 +++ .../tmf/ui/views/uml2sd/core/BasicFrame.java | 520 +++++ .../views/uml2sd/core/EllipsisisMessage.java | 127 ++ .../uml2sd/core/ExecutionOccurrence.java | 194 ++ .../tmf/ui/views/uml2sd/core/Frame.java | 921 +++++++++ .../tmf/ui/views/uml2sd/core/GraphNode.java | 713 +++++++ .../tmf/ui/views/uml2sd/core/HotSpot.java | 138 ++ .../tmf/ui/views/uml2sd/core/ITimeRange.java | 28 + .../tmf/ui/views/uml2sd/core/Lifeline.java | 468 +++++ .../views/uml2sd/core/LifelineCategories.java | 67 + .../tmf/ui/views/uml2sd/core/Metrics.java | 275 +++ .../tmf/ui/views/uml2sd/core/SDTimeEvent.java | 45 + .../tmf/ui/views/uml2sd/core/Stop.java | 139 ++ .../tmf/ui/views/uml2sd/core/SyncMessage.java | 263 +++ .../views/uml2sd/core/SyncMessageReturn.java | 87 + .../tmf/ui/views/uml2sd/drawings/IColor.java | 34 + .../tmf/ui/views/uml2sd/drawings/IFont.java | 34 + .../tmf/ui/views/uml2sd/drawings/IGC.java | 366 ++++ .../tmf/ui/views/uml2sd/drawings/IImage.java | 34 + .../views/uml2sd/drawings/ISDPreferences.java | 137 ++ .../views/uml2sd/drawings/impl/ColorImpl.java | 52 + .../views/uml2sd/drawings/impl/FontImpl.java | 53 + .../views/uml2sd/drawings/impl/ImageImpl.java | 71 + .../uml2sd/handlers/ConfigureMinMax.java | 44 + .../ui/views/uml2sd/handlers/FirstPage.java | 62 + .../uml2sd/handlers/KeyBindingsManager.java | 241 +++ .../ui/views/uml2sd/handlers/LastPage.java | 62 + .../ui/views/uml2sd/handlers/MoveSDDown.java | 71 + .../ui/views/uml2sd/handlers/MoveSDLeft.java | 72 + .../ui/views/uml2sd/handlers/MoveSDRight.java | 73 + .../ui/views/uml2sd/handlers/MoveSDUp.java | 71 + .../views/uml2sd/handlers/MoveToMessage.java | 116 ++ .../ui/views/uml2sd/handlers/NextPage.java | 64 + .../uml2sd/handlers/OpenSDFiltersDialog.java | 66 + .../uml2sd/handlers/OpenSDFindDialog.java | 90 + .../uml2sd/handlers/OpenSDPagesDialog.java | 65 + .../ui/views/uml2sd/handlers/PrevPage.java | 64 + .../tmf/ui/views/uml2sd/handlers/Print.java | 51 + .../ui/views/uml2sd/handlers/ShowNodeEnd.java | 90 + .../views/uml2sd/handlers/ShowNodeStart.java | 89 + .../tmf/ui/views/uml2sd/handlers/Zoom.java | 201 ++ .../provider/IExtendedFilterProvider.java | 25 + .../provider/IExtendedFindProvider.java | 25 + .../handlers/provider/IPropertySheetPage.java | 17 + .../provider/ISDAdvancedPagingProvider.java | 42 + .../provider/ISDCollapseProvider.java | 33 + .../ISDExtendedActionBarProvider.java | 33 + .../handlers/provider/ISDFilterProvider.java | 36 + .../handlers/provider/ISDFindProvider.java | 41 + .../provider/ISDGraphNodeSupporter.java | 47 + .../handlers/provider/ISDPagingProvider.java | 58 + .../provider/ISDPropertiesProvider.java | 27 + .../uml2sd/handlers/widgets/Criteria.java | 346 ++++ .../handlers/widgets/FilterCriteria.java | 187 ++ .../handlers/widgets/FilterListDialog.java | 443 +++++ .../uml2sd/handlers/widgets/PagesDialog.java | 150 ++ .../handlers/widgets/SearchFilterDialog.java | 350 ++++ .../uml2sd/handlers/widgets/TabContents.java | 399 ++++ .../tmf/ui/views/uml2sd/impl/Messages.java | 36 + .../ui/views/uml2sd/impl/TmfAsyncMessage.java | 62 + .../ui/views/uml2sd/impl/TmfSyncMessage.java | 61 + .../uml2sd/impl/TmfUml2SDSyncLoader.java | 1334 +++++++++++++ .../ui/views/uml2sd/impl/messages.properties | 11 + .../ui/views/uml2sd/load/IUml2SDLoader.java | 41 + .../ui/views/uml2sd/load/LoadersManager.java | 358 ++++ .../views/uml2sd/preferences/SDViewPref.java | 383 ++++ .../uml2sd/preferences/SDViewerPage.java | 432 ++++ .../ui/views/uml2sd/util/MinMaxDialog.java | 166 ++ .../tmf/ui/views/uml2sd/util/SDMessages.java | 175 ++ .../ui/views/uml2sd/util/SDPrintDialog.java | 130 ++ .../ui/views/uml2sd/util/SDPrintDialogUI.java | 1102 +++++++++++ .../uml2sd/util/SortAsyncForBackward.java | 87 + .../util/SortAsyncMessageComparator.java | 85 + .../util/SortSyncMessageComparator.java | 57 + .../uml2sd/util/TimeEventComparator.java | 50 + .../ui/views/uml2sd/util/messages.properties | 177 ++ .../META-INF/MANIFEST.MF | 5 +- org.eclipse.linuxtools.tmf/pom.xml | 2 +- .../tmf/component/TmfComponent.java | 4 + .../linuxtools/tmf/event/TmfTimestamp.java | 17 + .../uml2sd/ITmfAsyncSequenceDiagramEvent.java | 21 + .../uml2sd/ITmfSyncSequenceDiagramEvent.java | 37 + .../uml2sd/TmfAsyncSequenceDiagramEvent.java | 54 + .../uml2sd/TmfSyncSequenceDiagramEvent.java | 92 + 169 files changed, 25741 insertions(+), 189 deletions(-) create mode 100644 org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/uml2sd/AllTests.java create mode 100644 org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/uml2sd/TmfAsyncSequenceDiagramEventTest.java create mode 100644 org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/uml2sd/TmfSyncSequenceDiagramEventTest.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/AllTmfUITests.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/handlers/widgets/AllTests.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/handlers/widgets/CriteriaTest.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/AllTests.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/IUml2SDTestConstants.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/IUml2SdSignalValidator.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderExpTest.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderFilterTest.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderFindTest.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderPagesTest.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderSignalTest.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderTimeTest.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/Uml2SDSignalValidator.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/Uml2SDTestFacility.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/Uml2SDTestTimestamp.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/load/AllTests.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/load/LoadersManagerTest.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/tracesets/sdEvents create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/widgetStubs/org/eclipse/linuxtools/tmf/ui/tests/uml2sd/load/TestLoaders.java create mode 100644 org.eclipse.linuxtools.tmf.ui.tests/widgetStubs/org/eclipse/linuxtools/tmf/ui/tests/uml2sd/trace/TmfUml2SDTestTrace.java create mode 100644 org.eclipse.linuxtools.tmf.ui/.options create mode 100644 org.eclipse.linuxtools.tmf.ui/icons/elcl16/backward_nav.gif create mode 100644 org.eclipse.linuxtools.tmf.ui/icons/elcl16/forward_nav.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/elcl16/gotopage_menu.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/elcl16/next_menu.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/elcl16/node_end.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/elcl16/node_start.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/elcl16/prev_menu.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/elcl16/search_seqdiag_menu.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/elcl16/select_menu.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/eview16/sequencediagram_view.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/eview16/statistics_view.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/obj16/arrow_colapse.bmp create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/obj16/arrow_up.bmp create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/obj16/class.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/obj16/classobject_obj.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/obj16/collapsed_obj.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/obj16/node_obj.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/obj16/package_obj.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/obj16/plus_obj.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/obj16/process_obj.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/obj16/thread_obj.gif create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/obj16/zoom_mask.bmp create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/obj16/zoomin_obj.bmp create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/obj16/zoomout_obj.bmp create mode 100755 org.eclipse.linuxtools.tmf.ui/icons/ovr16/error_ovr.gif delete mode 100644 org.eclipse.linuxtools.tmf.ui/icons/wizban/trace_import_wiz.png create mode 100644 org.eclipse.linuxtools.tmf.ui/schema/org.eclipse.linuxtools.tmf.ui.uml2SDLoader.exsd create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/ITmfImageConstants.java create mode 100644 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/TmfUiTracer.java create mode 100644 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/preferences/TmfTracingPreferencePage.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/DiagramToolTip.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/DrawableToolTip.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/ITimeCompressionListener.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/NGC.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/SDView.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/SDWidget.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/SDWidgetSelectionProvider.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/ScrollView.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/TimeCompressionBar.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/AsyncMessage.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/AsyncMessageReturn.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/BaseMessage.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/BasicExecutionOccurrence.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/BasicFrame.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/EllipsisisMessage.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/ExecutionOccurrence.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Frame.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/GraphNode.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/HotSpot.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/ITimeRange.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Lifeline.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/LifelineCategories.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Metrics.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/SDTimeEvent.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Stop.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/SyncMessage.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/SyncMessageReturn.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IColor.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IFont.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IGC.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IImage.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/ISDPreferences.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/impl/ColorImpl.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/impl/FontImpl.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/impl/ImageImpl.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/ConfigureMinMax.java create mode 100644 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/FirstPage.java create mode 100644 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/KeyBindingsManager.java create mode 100644 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/LastPage.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDDown.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDLeft.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDRight.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDUp.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveToMessage.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/NextPage.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/OpenSDFiltersDialog.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/OpenSDFindDialog.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/OpenSDPagesDialog.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/PrevPage.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/Print.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/ShowNodeEnd.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/ShowNodeStart.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/Zoom.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/IExtendedFilterProvider.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/IExtendedFindProvider.java create mode 100644 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/IPropertySheetPage.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDAdvancedPagingProvider.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDCollapseProvider.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDExtendedActionBarProvider.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDFilterProvider.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDFindProvider.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDGraphNodeSupporter.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDPagingProvider.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDPropertiesProvider.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/Criteria.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/FilterCriteria.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/FilterListDialog.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/PagesDialog.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/SearchFilterDialog.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/TabContents.java create mode 100644 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/Messages.java create mode 100644 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/TmfAsyncMessage.java create mode 100644 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/TmfSyncMessage.java create mode 100644 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/TmfUml2SDSyncLoader.java create mode 100644 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/messages.properties create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/load/IUml2SDLoader.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/load/LoadersManager.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/preferences/SDViewPref.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/preferences/SDViewerPage.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/MinMaxDialog.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SDMessages.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SDPrintDialog.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SDPrintDialogUI.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SortAsyncForBackward.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SortAsyncMessageComparator.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SortSyncMessageComparator.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/TimeEventComparator.java create mode 100755 org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/messages.properties create mode 100644 org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/ITmfAsyncSequenceDiagramEvent.java create mode 100644 org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/ITmfSyncSequenceDiagramEvent.java create mode 100644 org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/TmfAsyncSequenceDiagramEvent.java create mode 100644 org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/TmfSyncSequenceDiagramEvent.java diff --git a/org.eclipse.linuxtools.lttng.tests/src/org/eclipse/linuxtools/lttng/tests/event/LttngTimestampTest.java b/org.eclipse.linuxtools.lttng.tests/src/org/eclipse/linuxtools/lttng/tests/event/LttngTimestampTest.java index 95c2509b07..20f01d8b33 100644 --- a/org.eclipse.linuxtools.lttng.tests/src/org/eclipse/linuxtools/lttng/tests/event/LttngTimestampTest.java +++ b/org.eclipse.linuxtools.lttng.tests/src/org/eclipse/linuxtools/lttng/tests/event/LttngTimestampTest.java @@ -138,6 +138,10 @@ public class LttngTimestampTest extends TestCase { assertEquals("toString()", expectedTS2, ts2.toString()); assertEquals("toString()", expectedTS3, ts3.toString()); assertEquals("toString()", expectedTS4, ts4.toString()); + + LttngTimestamp ts5 = new LttngTimestamp(2234567890L); + LttngTimestamp delta = ts4.getDelta(ts5); + String expectedDelta = "-1.000000000"; + assertEquals("toString()", expectedDelta, delta.toString()); } - } diff --git a/org.eclipse.linuxtools.lttng/META-INF/MANIFEST.MF b/org.eclipse.linuxtools.lttng/META-INF/MANIFEST.MF index c836b188bc..27817f3008 100644 --- a/org.eclipse.linuxtools.lttng/META-INF/MANIFEST.MF +++ b/org.eclipse.linuxtools.lttng/META-INF/MANIFEST.MF @@ -2,11 +2,11 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.linuxtools.lttng -Bundle-Version: 0.3.1.qualifier +Bundle-Version: 0.3.2.qualifier Bundle-Activator: org.eclipse.linuxtools.lttng.LTTngCorePlugin Bundle-Vendor: %Bundle-Vendor Require-Bundle: org.eclipse.core.runtime, - org.eclipse.linuxtools.tmf;bundle-version="0.3.0", + org.eclipse.linuxtools.tmf;bundle-version="0.3.2", org.eclipse.linuxtools.lttng.jni;bundle-version="0.3.0", org.eclipse.rse.core;bundle-version="3.1.101", org.eclipse.tm.tcf.core;bundle-version="0.3.0" diff --git a/org.eclipse.linuxtools.lttng/pom.xml b/org.eclipse.linuxtools.lttng/pom.xml index c9d223a977..25928f1b54 100644 --- a/org.eclipse.linuxtools.lttng/pom.xml +++ b/org.eclipse.linuxtools.lttng/pom.xml @@ -18,7 +18,7 @@ org.eclipse.linuxtools.lttng - 0.3.1-SNAPSHOT + 0.3.2-SNAPSHOT eclipse-plugin LTTng - Linux Tracing Toolkit (Incubation) diff --git a/org.eclipse.linuxtools.lttng/src/org/eclipse/linuxtools/lttng/event/LttngTimestamp.java b/org.eclipse.linuxtools.lttng/src/org/eclipse/linuxtools/lttng/event/LttngTimestamp.java index 6d7419ee78..bc93f392a3 100644 --- a/org.eclipse.linuxtools.lttng/src/org/eclipse/linuxtools/lttng/event/LttngTimestamp.java +++ b/org.eclipse.linuxtools.lttng/src/org/eclipse/linuxtools/lttng/event/LttngTimestamp.java @@ -141,7 +141,12 @@ public class LttngTimestamp extends TmfTimestamp { @SuppressWarnings("nls") public String toString() { - StringBuilder sb = new StringBuilder(String.valueOf(fValue)); + long value = fValue; + if (fValue < 0) { + value = -fValue; + } + + StringBuilder sb = new StringBuilder(String.valueOf(value)); // Prepend the correct number of "0" so we can insert a "." at the right location int nbZeroes = (-fScale) - sb.length() + 1; @@ -149,6 +154,12 @@ public class LttngTimestamp extends TmfTimestamp { sb.insert(i, "0"); } sb.insert(sb.length() + fScale, "."); + + // Prepend "-" if negative + if (fValue < 0) { + sb.insert(0, "-"); + } + return sb.toString(); } @@ -157,4 +168,16 @@ public class LttngTimestamp extends TmfTimestamp { return (LttngTimestamp) super.clone(); } + /** + * Compute the delta between two timestamps (adjusted to scale of current timestamp). + * + * @param reference the reference timestamp to synchronize with + * @return the delta timestamp + * @throws ArithmeticException + */ + @Override + public LttngTimestamp getDelta(TmfTimestamp other) throws ArithmeticException { + TmfTimestamp delta = super.getDelta(other); + return new LttngTimestamp(delta); + } } diff --git a/org.eclipse.linuxtools.tmf.tests/META-INF/MANIFEST.MF b/org.eclipse.linuxtools.tmf.tests/META-INF/MANIFEST.MF index 4e1d425d46..ab38540ad8 100644 --- a/org.eclipse.linuxtools.tmf.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.linuxtools.tmf.tests/META-INF/MANIFEST.MF @@ -11,3 +11,4 @@ Require-Bundle: org.junit;bundle-version="3.8.2", Bundle-Localization: plugin Bundle-ActivationPolicy: lazy Bundle-Activator: org.eclipse.linuxtools.tmf.tests.TmfCoreTestPlugin +Export-Package: org.eclipse.linuxtools.tmf.trace;x-friends:="org.eclipse.linuxtools.tmf.ui.tests" diff --git a/org.eclipse.linuxtools.tmf.tests/pom.xml b/org.eclipse.linuxtools.tmf.tests/pom.xml index 8b0e25c2dc..c8363b2ac5 100644 --- a/org.eclipse.linuxtools.tmf.tests/pom.xml +++ b/org.eclipse.linuxtools.tmf.tests/pom.xml @@ -1,14 +1,16 @@ - + + 4.0.0 @@ -19,7 +21,7 @@ org.eclipse.linuxtools.tmf.tests 0.3.0-SNAPSHOT - eclipse-plugin + eclipse-test-plugin Linux Tools TMF Tests (LTTng) Plug-in @@ -36,7 +38,15 @@ org.eclipse.tycho - tycho-source-plugin + tycho-surefire-plugin + ${tycho-version} + + org.eclipse.linuxtools.tmf.tests + org.eclipse.linuxtools.tmf.tests.AllTmfCoreTests + false + false + org.eclipse.sdk.ide + org.codehaus.mojo diff --git a/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/AllTmfCoreTests.java b/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/AllTmfCoreTests.java index d6f4d82cf0..340e2f7d7f 100644 --- a/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/AllTmfCoreTests.java +++ b/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/AllTmfCoreTests.java @@ -31,6 +31,7 @@ public class AllTmfCoreTests { suite.addTest(org.eclipse.linuxtools.tmf.tests.component.AllTests.suite()); suite.addTest(org.eclipse.linuxtools.tmf.tests.trace.AllTests.suite()); suite.addTest(org.eclipse.linuxtools.tmf.tests.experiment.AllTests.suite()); + suite.addTest(org.eclipse.linuxtools.tmf.tests.uml2sd.AllTests.suite()); //$JUnit-END$ return suite; } diff --git a/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/event/TmfTimestampTest.java b/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/event/TmfTimestampTest.java index d4a3e65e45..f3b7503634 100644 --- a/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/event/TmfTimestampTest.java +++ b/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/event/TmfTimestampTest.java @@ -418,4 +418,78 @@ public class TmfTimestampTest extends TestCase { assertEquals("CompareTo", -1, ts4.compareTo(TmfTimestamp.BigCrunch, false)); } + // ------------------------------------------------------------------------ + // getDelta + // ------------------------------------------------------------------------ + + public void testDelta() throws Exception { + // Delta for same scale and precision (delta > 0) + TmfTimestamp ts0 = new TmfTimestamp(10, (byte)9); + TmfTimestamp ts1 = new TmfTimestamp(5, (byte)9); + TmfTimestamp exp = new TmfTimestamp(5, (byte)9); + + TmfTimestamp delta = ts0.getDelta(ts1); + assertEquals("getDelta", 0, delta.compareTo(exp, false)); + + // Delta for same scale and precision (delta < 0) + ts0 = new TmfTimestamp(5, (byte)9); + ts1 = new TmfTimestamp(10, (byte)9); + exp = new TmfTimestamp(-5, (byte)9); + + delta = ts0.getDelta(ts1); + assertEquals("getDelta", 0, delta.compareTo(exp, false)); + + // Delta for different scale and same precision (delta > 0) + ts0 = new TmfTimestamp(5, (byte)9); + ts1 = new TmfTimestamp(10, (byte)8); + exp = new TmfTimestamp(4, (byte)9); + + delta = ts0.getDelta(ts1); + assertEquals("getDelta", 0, delta.compareTo(exp, false)); + + // Delta for different scale and same precision (delta > 0) + ts0 = new TmfTimestamp(5, (byte)9); + ts1 = new TmfTimestamp(10, (byte)7); + exp = new TmfTimestamp(5, (byte)9); + + delta = ts0.getDelta(ts1); + assertEquals("getDelta", 0, delta.compareTo(exp, false)); + + // Delta for different scale and same precision + ts0 = new TmfTimestamp(10, (byte)9); + ts1 = new TmfTimestamp(5, (byte)8); + exp = new TmfTimestamp(10, (byte)9); + + delta = ts0.getDelta(ts1); + assertEquals("getDelta", 0, delta.compareTo(exp, false)); + + // Delta for same scale and different precision + ts0 = new TmfTimestamp(10, (byte)9,1); + ts1 = new TmfTimestamp(5, (byte)9, 2); + exp = new TmfTimestamp(5, (byte)9, 2); + + delta = ts0.getDelta(ts1); + assertEquals("getDelta", 0, delta.compareTo(exp, true)); + assertEquals("precision", 2, delta.getPrecision()); + + // Delta for same scale and different precision + ts0 = new TmfTimestamp(5, (byte)9, 2); + ts1 = new TmfTimestamp(10, (byte)9, 1); + exp = new TmfTimestamp(-5, (byte)9, 2); + + delta = ts0.getDelta(ts1); + assertEquals("getDelta", 0, delta.compareTo(exp, true)); + assertEquals("precision", 2, delta.getPrecision()); + + // Delta for different scale and different precision + ts0 = new TmfTimestamp(5, (byte)9, 2); + ts1 = new TmfTimestamp(10, (byte)8, 1); + exp = new TmfTimestamp(4, (byte)9, 2); + assertEquals("precision", 2, delta.getPrecision()); + assertEquals("precision", 2, delta.getPrecision()); + + delta = ts0.getDelta(ts1); + assertEquals("getDelta", 0, delta.compareTo(exp, true)); + assertEquals("precision", 2, delta.getPrecision()); + } } diff --git a/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/uml2sd/AllTests.java b/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/uml2sd/AllTests.java new file mode 100644 index 0000000000..dd62f3d684 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/uml2sd/AllTests.java @@ -0,0 +1,20 @@ +package org.eclipse.linuxtools.tmf.tests.uml2sd; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.eclipse.linuxtools.tmf.TmfCorePlugin; + +@SuppressWarnings("nls") +public class AllTests { + + public static Test suite() { + TestSuite suite = new TestSuite("Test suite for " + TmfCorePlugin.PLUGIN_ID + ".uml2sd"); //$NON-NLS-1$); + //$JUnit-BEGIN$ + suite.addTestSuite(TmfSyncSequenceDiagramEventTest.class); + suite.addTestSuite(TmfAsyncSequenceDiagramEventTest.class); + //$JUnit-END$ + return suite; + } + +} \ No newline at end of file diff --git a/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/uml2sd/TmfAsyncSequenceDiagramEventTest.java b/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/uml2sd/TmfAsyncSequenceDiagramEventTest.java new file mode 100644 index 0000000000..2293130065 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/uml2sd/TmfAsyncSequenceDiagramEventTest.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.tests.uml2sd; + +import junit.framework.TestCase; + +import org.eclipse.linuxtools.tmf.event.TmfEvent; +import org.eclipse.linuxtools.tmf.event.TmfEventContent; +import org.eclipse.linuxtools.tmf.event.TmfEventReference; +import org.eclipse.linuxtools.tmf.event.TmfEventSource; +import org.eclipse.linuxtools.tmf.event.TmfEventType; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.uml2sd.TmfAsyncSequenceDiagramEvent; + +@SuppressWarnings("nls") +public class TmfAsyncSequenceDiagramEventTest extends TestCase { + + private final String fTypeId = "Some type"; + private final String fLabel0 = "label1"; + private final String fLabel1 = "label2"; + private final String[] fLabels = new String[] { fLabel0, fLabel1 }; + + private final TmfTimestamp fTimestamp1 = new TmfTimestamp(12345, (byte) 2, 5); + private final TmfTimestamp fTimestamp2 = new TmfTimestamp(12350, (byte) 2, 5); + private final TmfEventSource fSource = new TmfEventSource("Source"); + private final TmfEventType fType = new TmfEventType(fTypeId, fLabels); + private final TmfEventReference fReference = new TmfEventReference("Some reference"); + + private final TmfEvent fEvent1; + private final TmfEvent fEvent2; + private final TmfEventContent fContent1; + private final TmfEventContent fContent2; + + + public TmfAsyncSequenceDiagramEventTest() { + fEvent1 = new TmfEvent(fTimestamp1, fSource, fType, fReference); + fContent1 = new TmfEventContent(fEvent1, "Some content"); + fEvent1.setContent(fContent1); + + fEvent2 = new TmfEvent(fTimestamp1, fTimestamp2, fSource, fType, fReference); + fContent2 = new TmfEventContent(fEvent2, "Some other content"); + fEvent2.setContent(fContent2); + } + + @Override + public void setUp() throws Exception { + } + + @Override + public void tearDown() throws Exception { + } + + public void testTmfAsyncSequenceDiagramEvent() { + TmfAsyncSequenceDiagramEvent event = null; + + // Check for illegal arguments (i.e. null for the parameters) + try { + event = new TmfAsyncSequenceDiagramEvent(null, null, null, null, null); + fail(); + } catch (IllegalArgumentException e) { + // success + assertTrue("TmfAsyncSequenceDiagramEvent", e.getMessage().contains("startEvent=null")); + } + + try { + event = new TmfAsyncSequenceDiagramEvent(fEvent1, fEvent2, null, null, null); + fail(); + } catch (IllegalArgumentException e) { + // success + assertTrue("TmfAsyncSequenceDiagramEvent", e.getMessage().contains("sender=null")); + } + + try { + event = new TmfAsyncSequenceDiagramEvent(fEvent1, fEvent2, null, null, null); + fail(); + } catch (IllegalArgumentException e) { + // success + assertTrue("TmfAsyncSequenceDiagramEvent", e.getMessage().contains("receiver=null")); + } + + try { + event = new TmfAsyncSequenceDiagramEvent(fEvent1, fEvent2, "sender", null, null); + fail(); + } catch (IllegalArgumentException e) { + // success + assertTrue("TmfAsyncSequenceDiagramEvent", e.getMessage().contains("name=null")); + } + + try { + event = new TmfAsyncSequenceDiagramEvent(fEvent1, null, "sender", "receiver", "signal"); + fail(); + } catch (IllegalArgumentException e) { + // success + assertTrue("TmfAsyncSequenceDiagramEvent", e.getMessage().contains("endEvent=null")); + } + + try { + event = new TmfAsyncSequenceDiagramEvent(fEvent1, fEvent2, "sender", "receiver", "signal"); + // success + assertEquals("testTmfAsyncSequenceDiagramEvent", 0, event.getStartTime().compareTo(fTimestamp1, true)); + assertEquals("testTmfAsyncSequenceDiagramEvent", 0, event.getEndTime().compareTo(fTimestamp2, true)); + assertEquals("testTmfAsyncSequenceDiagramEvent", "sender", event.getSender()); + assertEquals("testTmfAsyncSequenceDiagramEvent", "receiver", event.getReceiver()); + assertEquals("testTmfAsyncSequenceDiagramEvent", "signal", event.getName()); + + } catch (IllegalArgumentException e) { + fail(); + } + } +} diff --git a/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/uml2sd/TmfSyncSequenceDiagramEventTest.java b/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/uml2sd/TmfSyncSequenceDiagramEventTest.java new file mode 100644 index 0000000000..7941aaf30c --- /dev/null +++ b/org.eclipse.linuxtools.tmf.tests/src/org/eclipse/linuxtools/tmf/tests/uml2sd/TmfSyncSequenceDiagramEventTest.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.tests.uml2sd; + +import junit.framework.TestCase; + +import org.eclipse.linuxtools.tmf.event.TmfEvent; +import org.eclipse.linuxtools.tmf.event.TmfEventContent; +import org.eclipse.linuxtools.tmf.event.TmfEventReference; +import org.eclipse.linuxtools.tmf.event.TmfEventSource; +import org.eclipse.linuxtools.tmf.event.TmfEventType; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.uml2sd.TmfSyncSequenceDiagramEvent; + +@SuppressWarnings("nls") +public class TmfSyncSequenceDiagramEventTest extends TestCase { + + private final String fTypeId = "Some type"; + private final String fLabel0 = "label1"; + private final String fLabel1 = "label2"; + private final String[] fLabels = new String[] { fLabel0, fLabel1 }; + + private final TmfTimestamp fTimestamp1 = new TmfTimestamp(12345, (byte) 2, 5); + private final TmfEventSource fSource = new TmfEventSource("Source"); + private final TmfEventType fType = new TmfEventType(fTypeId, fLabels); + private final TmfEventReference fReference = new TmfEventReference("Some reference"); + + private final TmfEvent fEvent1; + private final TmfEventContent fContent1; + + public TmfSyncSequenceDiagramEventTest () { + fEvent1 = new TmfEvent(fTimestamp1, fSource, fType, fReference); + fContent1 = new TmfEventContent(fEvent1, "Some content"); + fEvent1.setContent(fContent1); + } + + @Override + public void setUp() throws Exception { + } + + @Override + public void tearDown() throws Exception { + } + + public void testTmfSyncSequenceDiagramEvent() { + TmfSyncSequenceDiagramEvent event = null; + try { + event = new TmfSyncSequenceDiagramEvent(null, null, null, null); + fail(); + } catch (IllegalArgumentException e) { + // success + assertTrue("testTmfSyncSequenceDiagramEvent", e.getMessage().contains("startEvent=null")); + } + + try { + event = new TmfSyncSequenceDiagramEvent(fEvent1, null, null, null); + fail(); + } catch (IllegalArgumentException e) { + // success + assertTrue("testTmfSyncSequenceDiagramEvent", e.getMessage().contains("sender=null")); + } + + try { + event = new TmfSyncSequenceDiagramEvent(fEvent1, "sender", null, null); + fail(); + } catch (IllegalArgumentException e) { + // success + assertTrue("testTmfSyncSequenceDiagramEvent", e.getMessage().contains("receiver=null")); + } + + try { + event = new TmfSyncSequenceDiagramEvent(fEvent1, "sender", "receiver", null); + fail(); + } catch (IllegalArgumentException e) { + // success + assertTrue("testTmfSyncSequenceDiagramEvent", e.getMessage().contains("name=null")); + } + + try { + event = new TmfSyncSequenceDiagramEvent(fEvent1, "sender", "receiver", "signal"); + // success + assertEquals("testTmfSyncSequenceDiagramEvent", 0, event.getStartTime().compareTo(fTimestamp1, true)); + assertEquals("testTmfSyncSequenceDiagramEvent", "sender", event.getSender()); + assertEquals("testTmfSyncSequenceDiagramEvent", "receiver", event.getReceiver()); + assertEquals("testTmfSyncSequenceDiagramEvent", "signal", event.getName()); + + } catch (IllegalArgumentException e) { + fail(); + } + } +} diff --git a/org.eclipse.linuxtools.tmf.tests/stubs/org/eclipse/linuxtools/tmf/trace/TmfEventParserStub.java b/org.eclipse.linuxtools.tmf.tests/stubs/org/eclipse/linuxtools/tmf/trace/TmfEventParserStub.java index ddc53dd5a0..eab0891473 100644 --- a/org.eclipse.linuxtools.tmf.tests/stubs/org/eclipse/linuxtools/tmf/trace/TmfEventParserStub.java +++ b/org.eclipse.linuxtools.tmf.tests/stubs/org/eclipse/linuxtools/tmf/trace/TmfEventParserStub.java @@ -74,44 +74,44 @@ public class TmfEventParserStub implements ITmfEventParser { String name = eventStream.getName(); name = name.substring(name.lastIndexOf('/') + 1); - synchronized(stream) { - long location = 0; - if (context != null) - location = ((TmfLocation) (context.getLocation())).getLocation(); - stream.seek(location); - - try { - long ts = stream.readLong(); - String source = stream.readUTF(); - String type = stream.readUTF(); - @SuppressWarnings("unused") - int reference = stream.readInt(); - int typeIndex = Integer.parseInt(type.substring(typePrefix.length())); - String[] fields = new String[typeIndex]; - for (int i = 0; i < typeIndex; i++) { - fields[i] = stream.readUTF(); - } - - String content = "["; - if (typeIndex > 0) { - content += fields[0]; - } - for (int i = 1; i < typeIndex; i++) { - content += ", " + fields[i]; - } - content += "]"; - - TmfEvent event = new TmfEvent( - new TmfTimestamp(ts, (byte) -3, 0), // millisecs - new TmfEventSource(source), - fTypes[typeIndex], - new TmfEventReference(name)); - TmfEventContent cnt = new TmfEventContent(event, content); - event.setContent(cnt); - return event; - } catch (EOFException e) { - } - } + // no need to use synchronized since it's already cover by the calling method + + long location = 0; + if (context != null) + location = ((TmfLocation) (context.getLocation())).getLocation(); + stream.seek(location); + + try { + long ts = stream.readLong(); + String source = stream.readUTF(); + String type = stream.readUTF(); + @SuppressWarnings("unused") + int reference = stream.readInt(); + int typeIndex = Integer.parseInt(type.substring(typePrefix.length())); + String[] fields = new String[typeIndex]; + for (int i = 0; i < typeIndex; i++) { + fields[i] = stream.readUTF(); + } + + String content = "["; + if (typeIndex > 0) { + content += fields[0]; + } + for (int i = 1; i < typeIndex; i++) { + content += ", " + fields[i]; + } + content += "]"; + + TmfEvent event = new TmfEvent( + new TmfTimestamp(ts, (byte) -3, 0), // millisecs + new TmfEventSource(source), + fTypes[typeIndex], + new TmfEventReference(name)); + TmfEventContent cnt = new TmfEventContent(event, content); + event.setContent(cnt); + return event; + } catch (EOFException e) { + } return null; } diff --git a/org.eclipse.linuxtools.tmf.tests/stubs/org/eclipse/linuxtools/tmf/trace/TmfTraceStub.java b/org.eclipse.linuxtools.tmf.tests/stubs/org/eclipse/linuxtools/tmf/trace/TmfTraceStub.java index 7bab7ba5c6..16ef92042e 100644 --- a/org.eclipse.linuxtools.tmf.tests/stubs/org/eclipse/linuxtools/tmf/trace/TmfTraceStub.java +++ b/org.eclipse.linuxtools.tmf.tests/stubs/org/eclipse/linuxtools/tmf/trace/TmfTraceStub.java @@ -15,6 +15,7 @@ package org.eclipse.linuxtools.tmf.trace; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; +import java.util.concurrent.locks.ReentrantLock; import org.eclipse.linuxtools.tmf.event.TmfEvent; import org.eclipse.linuxtools.tmf.event.TmfTimeRange; @@ -39,6 +40,9 @@ public class TmfTraceStub extends TmfTrace { // The associated event parser private ITmfEventParser fParser; + // The synchronization lock + private ReentrantLock fLock = new ReentrantLock(); + // ------------------------------------------------------------------------ // Constructors // ------------------------------------------------------------------------ @@ -81,6 +85,20 @@ public class TmfTraceStub extends TmfTrace { fParser = new TmfEventParserStub(); } + + /** + * @param filename + * @param cacheSize + * @param waitForCompletion + * @param parser + * @throws FileNotFoundException + */ + public TmfTraceStub(String filename, int cacheSize, boolean waitForCompletion, ITmfEventParser parser) throws FileNotFoundException { + super(filename, TmfEvent.class, filename, cacheSize, false); + fTrace = new RandomAccessFile(filename, "r"); + fParser = parser; + } + /** */ @Override @@ -118,73 +136,98 @@ public class TmfTraceStub extends TmfTrace { @Override @SuppressWarnings("unchecked") public TmfContext seekLocation(ITmfLocation location) { + fLock.lock(); try { - synchronized(fTrace) { - // Position the trace at the requested location and - // returns the corresponding context - long loc = 0; - long rank = 0; - if (location != null) { - loc = ((TmfLocation) location).getLocation(); - rank = ITmfContext.UNKNOWN_RANK; - } - if (loc != fTrace.getFilePointer()) { - fTrace.seek(loc); - } - TmfContext context = new TmfContext(getCurrentLocation(), rank); - return context; - } + if (fTrace != null) { + // Position the trace at the requested location and + // returns the corresponding context + long loc = 0; + long rank = 0; + if (location != null) { + loc = ((TmfLocation) location).getLocation(); + rank = ITmfContext.UNKNOWN_RANK; + } + if (loc != fTrace.getFilePointer()) { + fTrace.seek(loc); + } + TmfContext context = new TmfContext(getCurrentLocation(), rank); + return context; + } } catch (IOException e) { e.printStackTrace(); } + finally{ + fLock.unlock(); + } return null; } @Override public TmfContext seekLocation(double ratio) { + fLock.lock(); try { - ITmfLocation location = new TmfLocation(new Long((long) (ratio * fTrace.length()))); - TmfContext context = seekLocation(location); - context.setRank(ITmfContext.UNKNOWN_RANK); - return context; + if (fTrace != null) { + ITmfLocation location = new TmfLocation(new Long((long) (ratio * fTrace.length()))); + TmfContext context = seekLocation(location); + context.setRank(ITmfContext.UNKNOWN_RANK); + return context; + } } catch (IOException e) { e.printStackTrace(); + } finally { + fLock.unlock(); } + return null; } @Override public double getLocationRatio(ITmfLocation location) { + fLock.lock(); try { - if (location.getLocation() instanceof Long) { - return (double) ((Long) location.getLocation()) / fTrace.length(); + if (fTrace != null) { + if (location.getLocation() instanceof Long) { + return (double) ((Long) location.getLocation()) / fTrace.length(); + } } } catch (IOException e) { e.printStackTrace(); + } finally { + fLock.unlock(); } return 0; } @Override public TmfLocation getCurrentLocation() { + fLock.lock(); try { - return new TmfLocation(fTrace.getFilePointer()); + if (fTrace != null) { + return new TmfLocation(fTrace.getFilePointer()); + } } catch (IOException e) { e.printStackTrace(); + } finally { + fLock.unlock(); } return null; } @Override public TmfEvent parseEvent(TmfContext context) { + fLock.lock(); try { // parseNextEvent will update the context - TmfEvent event = fParser.parseNextEvent(this, context.clone()); - return event; + if (fTrace != null) { + TmfEvent event = fParser.parseNextEvent(this, context.clone()); + return event; + } } catch (IOException e) { e.printStackTrace(); + } finally { + fLock.unlock(); } return null; } @@ -203,5 +246,21 @@ public class TmfTraceStub extends TmfTrace { public void setEndTime(TmfTimestamp endTime) { super.setEndTime(endTime); } + + @Override + public void dispose() { + fLock.lock(); + try { + if (fTrace != null) { + fTrace.close(); + fTrace = null; + } + } catch (IOException e) { + // Ignore + } finally { + fLock.unlock(); + } + super.dispose(); + } } \ No newline at end of file diff --git a/org.eclipse.linuxtools.tmf.ui.tests/META-INF/MANIFEST.MF b/org.eclipse.linuxtools.tmf.ui.tests/META-INF/MANIFEST.MF index 7e111f72ed..248c4be27a 100644 --- a/org.eclipse.linuxtools.tmf.ui.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.linuxtools.tmf.ui.tests/META-INF/MANIFEST.MF @@ -9,6 +9,9 @@ Require-Bundle: org.junit, org.eclipse.linuxtools.tmf;bundle-version="0.2.0", org.eclipse.linuxtools.tmf.ui;bundle-version="0.2.0", org.eclipse.core.runtime;bundle-version="3.5.0", - org.eclipse.ui;bundle-version="3.5.0" + org.eclipse.ui;bundle-version="3.5.0", + org.eclipse.linuxtools.tmf.tests;bundle-version="0.3.0" Bundle-Localization: plugin Bundle-Activator: org.eclipse.linuxtools.tmf.ui.tests.TmfUITestPlugin +Bundle-ActivationPolicy: lazy + diff --git a/org.eclipse.linuxtools.tmf.ui.tests/plugin.xml b/org.eclipse.linuxtools.tmf.ui.tests/plugin.xml index 66e15b376c..33f04cedb5 100644 --- a/org.eclipse.linuxtools.tmf.ui.tests/plugin.xml +++ b/org.eclipse.linuxtools.tmf.ui.tests/plugin.xml @@ -10,6 +10,50 @@ id="org.eclipse.linuxtools.tmf.ui.widgets.timeAnalysis.test.stub.views.TmfTaTimeAnalysis" name="TMF Time Analysis View" restorable="true"> + + + + + + + + + + + + + + + + diff --git a/org.eclipse.linuxtools.tmf.ui.tests/pom.xml b/org.eclipse.linuxtools.tmf.ui.tests/pom.xml index f99bf03067..8ab2d67f5a 100644 --- a/org.eclipse.linuxtools.tmf.ui.tests/pom.xml +++ b/org.eclipse.linuxtools.tmf.ui.tests/pom.xml @@ -1,14 +1,16 @@ - + + 4.0.0 @@ -19,7 +21,7 @@ org.eclipse.linuxtools.tmf.ui.tests 0.3.0-SNAPSHOT - eclipse-plugin + eclipse-test-plugin Linux Tools TMF UI Tests (LTTng) Plug-in @@ -36,7 +38,15 @@ org.eclipse.tycho - tycho-source-plugin + tycho-surefire-plugin + ${tycho-version} + + org.eclipse.linuxtools.tmf.ui.tests + org.eclipse.linuxtools.tmf.ui.tests.AllTmfUITests + true + true + org.eclipse.sdk.ide + org.codehaus.mojo diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/AllTmfUITests.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/AllTmfUITests.java new file mode 100644 index 0000000000..392fe0bfe9 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/AllTmfUITests.java @@ -0,0 +1,17 @@ +package org.eclipse.linuxtools.tmf.ui.tests; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTmfUITests { + public static Test suite() { + TestSuite suite = new TestSuite(AllTmfUITests.class.getName()); + //$JUnit-BEGIN$ + suite.addTest(org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.handlers.widgets.AllTests.suite()); + suite.addTest(org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl.AllTests.suite()); + suite.addTest(org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.load.AllTests.suite()); + //$JUnit-END$ + return suite; + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/handlers/widgets/AllTests.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/handlers/widgets/AllTests.java new file mode 100644 index 0000000000..2fe2c125ee --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/handlers/widgets/AllTests.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.handlers.widgets; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + + public static Test suite() { + + TestSuite suite = new TestSuite(AllTests.class.getName()); + //$JUnit-BEGIN$ + suite.addTestSuite(CriteriaTest.class); + //$JUnit-END$ + return suite; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/handlers/widgets/CriteriaTest.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/handlers/widgets/CriteriaTest.java new file mode 100644 index 0000000000..2fc6d7bfd4 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/handlers/widgets/CriteriaTest.java @@ -0,0 +1,276 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.handlers.widgets; + +import java.util.List; +import java.util.regex.Pattern; + +import junit.framework.TestCase; + +import org.eclipse.jface.dialogs.DialogSettings; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDFilterProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDGraphNodeSupporter; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.Criteria; + +public class CriteriaTest extends TestCase { + + @Override + public void setUp() throws Exception { + } + + @Override + public void tearDown() throws Exception { + } + + @SuppressWarnings("nls") + public void testCriteria() { + Criteria criteria = new Criteria(); + assertFalse("testCriteria", criteria.isAsyncMessageReturnSelected()); + assertFalse("testCriteria", criteria.isAsyncMessageSelected()); + assertFalse("testCriteria", criteria.isCaseSenstiveSelected()); + assertFalse("testCriteria", criteria.isLifeLineSelected()); + assertFalse("testCriteria", criteria.isStopSelected()); + assertFalse("testCriteria", criteria.isSyncMessageReturnSelected()); + assertFalse("testCriteria", criteria.isSyncMessageSelected()); + assertNull("testCriteria", criteria.getExpression()); + assertNull("testCriteria", criteria.getPattern()); + } + + @SuppressWarnings("nls") + public void testCriteriaCriteria() { + Criteria criteria = new Criteria(); + criteria.setExpression("test"); + criteria.setLifeLineSelected(true); + criteria.setSyncMessageSelected(true); + + Criteria copy = new Criteria(criteria); + + assertEquals("testCriteriaCriteria", criteria.isAsyncMessageReturnSelected(), copy.isAsyncMessageReturnSelected()); + assertEquals("testCriteriaCriteria", criteria.isAsyncMessageSelected(), copy.isAsyncMessageSelected()); + assertEquals("testCriteriaCriteria", criteria.isCaseSenstiveSelected(), copy.isCaseSenstiveSelected()); + assertEquals("testCriteriaCriteria", criteria.isLifeLineSelected(), copy.isLifeLineSelected()); + assertEquals("testCriteriaCriteria", criteria.isStopSelected(), copy.isStopSelected()); + assertEquals("testCriteriaCriteria", criteria.isSyncMessageReturnSelected(), copy.isSyncMessageReturnSelected()); + assertEquals("testCriteriaCriteria", criteria.isSyncMessageSelected(), copy.isSyncMessageSelected()); + assertEquals("testCriteriaCriteria", criteria.getExpression(), copy.getExpression()); + assertEquals("testCriteriaCriteria", criteria.getPattern().pattern(), copy.getPattern().pattern()); + } + + @SuppressWarnings("nls") + public void testAccessors() { + Criteria criteria = new Criteria(); + criteria.setAsyncMessageReturnSelected(true); + criteria.setAsyncMessageSelected(true); + criteria.setCaseSenstiveSelected(true); + criteria.setLifeLineSelected(true); + criteria.setStopSelected(true); + criteria.setSyncMessageReturnSelected(true); + criteria.setSyncMessageSelected(true); + criteria.setExpression("test.*"); + + // set true to all flags + assertTrue("testAccessors", criteria.isAsyncMessageReturnSelected()); + assertTrue("testAccessors", criteria.isAsyncMessageSelected()); + assertTrue("testAccessors", criteria.isCaseSenstiveSelected()); + assertTrue("testAccessors", criteria.isLifeLineSelected()); + assertTrue("testAccessors", criteria.isStopSelected()); + assertTrue("testAccessors", criteria.isSyncMessageReturnSelected()); + assertTrue("testAccessors", criteria.isSyncMessageSelected()); + assertEquals("testAccessors", "test.*", criteria.getExpression()); + assertNotNull("testAccessors", criteria.getPattern()); + assertEquals("testAccessors", "test.*", criteria.getPattern().pattern()); + assertEquals("testAccessors", 0, criteria.getPattern().flags() & Pattern.CASE_INSENSITIVE); + + // set false to all flags + criteria.setAsyncMessageReturnSelected(false); + criteria.setAsyncMessageSelected(false); + criteria.setCaseSenstiveSelected(false); + criteria.setLifeLineSelected(false); + criteria.setStopSelected(false); + criteria.setSyncMessageReturnSelected(false); + criteria.setSyncMessageSelected(false); + + assertFalse("testAccessors", criteria.isAsyncMessageReturnSelected()); + assertFalse("testAccessors", criteria.isAsyncMessageSelected()); + assertFalse("testAccessors", criteria.isCaseSenstiveSelected()); + assertFalse("testAccessors", criteria.isLifeLineSelected()); + assertFalse("testAccessors", criteria.isStopSelected()); + assertFalse("testAccessors", criteria.isSyncMessageReturnSelected()); + assertFalse("testAccessors", criteria.isSyncMessageSelected()); + assertEquals("testAccessors", "test.*", criteria.getExpression()); + assertNotNull("testAccessors", criteria.getPattern()); + assertEquals("testAccessors", "test.*", criteria.getPattern().pattern()); + assertEquals("testAccessors", Pattern.CASE_INSENSITIVE, criteria.getPattern().flags() & Pattern.CASE_INSENSITIVE); + } + + @SuppressWarnings("nls") + public void testCompareTo() { + Criteria criteria = new Criteria(); + criteria.setExpression("test"); + criteria.setLifeLineSelected(true); + criteria.setSyncMessageSelected(true); + + Criteria copy = new Criteria(criteria); + assertTrue("testCompareTo", criteria.compareTo(copy)); + assertTrue("testCompareTo", copy.compareTo(criteria)); + assertTrue("testCompareTo", criteria.compareTo(criteria)); + + copy.setExpression(null); + assertFalse("testCompareTo", criteria.compareTo(copy)); + assertFalse("testCompareTo", copy.compareTo(criteria)); + + criteria.setExpression(null); + assertTrue("testCompareTo", criteria.compareTo(copy)); + assertTrue("testCompareTo", copy.compareTo(criteria)); + + criteria.setExpression("test"); + copy.setExpression("test.*[12345]"); + assertFalse("testCompareTo", criteria.compareTo(copy)); + assertFalse("testCompareTo", copy.compareTo(criteria)); + + copy.setExpression("test"); + copy.setAsyncMessageReturnSelected(true); + assertFalse("testCompareTo", criteria.compareTo(copy)); + assertFalse("testCompareTo", copy.compareTo(criteria)); + } + + @SuppressWarnings("nls") + public void testSave() { + DialogSettings settings = new DialogSettings("mysettings"); + + Criteria criteria = new Criteria(); + criteria.setExpression("test"); + criteria.setLifeLineSelected(true); + criteria.setSyncMessageSelected(true); + + // Save the criteria to the dialog settings + criteria.save(settings); + + // Check if all values are saved as expected + assertEquals("testSave", "test", settings.get("expression")); + assertEquals("testSave", "false", settings.get("isCaseSenstiveSelected")); + assertEquals("testSave", "false", settings.get("isAsyncMessageReturnSelected")); + assertEquals("testSave", "false", settings.get("isAsyncMessageSelected")); + assertEquals("testSave", "true", settings.get("isLifeLineSelected")); + assertEquals("testSave", "false", settings.get("isStopSelected")); + assertEquals("testSave", "false", settings.get("isSyncMessageReturnSelected")); + assertEquals("testSave", "true", settings.get("isSyncMessageSelected")); + } + + @SuppressWarnings("nls") + public void testLoad() { + DialogSettings settings = new DialogSettings("mysettings"); + + Criteria criteria = new Criteria(); + criteria.setExpression("test"); + criteria.setLifeLineSelected(true); + criteria.setSyncMessageSelected(true); + + // Save the criteria to the dialog settings + criteria.save(settings); + + // Load the criteria from the dialog settings + Criteria copy = new Criteria(); + copy.load(settings); + + assertTrue("testCompareTo", criteria.compareTo(copy)); + assertTrue("testCompareTo", copy.compareTo(criteria)); + } + @SuppressWarnings("nls") + public void testGetGraphNodeSummary() { + + // Create a dummy provider + ISDFilterProvider provider = new ISDFilterProvider() { + + @Override + public boolean isNodeSupported(int nodeType) { + return true; + } + + @Override + public String getNodeName(int nodeType, String loaderClassName) { + // not clear about purpose loaderClassName + switch (nodeType) { + case ISDGraphNodeSupporter.LIFELINE: + return "MyLifeline"; + case ISDGraphNodeSupporter.SYNCMESSAGE: + return "MySyncMessage"; + case ISDGraphNodeSupporter.SYNCMESSAGERETURN: + return "MySyncMessageReturn"; + case ISDGraphNodeSupporter.ASYNCMESSAGE: + return "MyAsyncMessage"; + case ISDGraphNodeSupporter.ASYNCMESSAGERETURN: + return "MyAsyncMessageReturn"; + case ISDGraphNodeSupporter.STOP: + return "MyStop"; + default: + return ""; + } + } + + @Override + public boolean filter(List filters) { + return false; + } + }; + + Criteria criteria = new Criteria(); + criteria.setExpression("BALL_.*"); + criteria.setAsyncMessageReturnSelected(true); + criteria.setAsyncMessageSelected(true); + criteria.setLifeLineSelected(true); + criteria.setStopSelected(true); + criteria.setSyncMessageReturnSelected(true); + criteria.setSyncMessageSelected(true); + + // Test summary when provider is available + String summary = criteria.getGraphNodeSummary(provider, null); + assertEquals("testGetGraphNodeSummary", "[MyLifeline or MySyncMessage or MySyncMessageReturn or MyAsyncMessage or MyAsyncMessageReturn or MyStop]", summary); + + // Test default summary when no provider is provided + summary = criteria.getGraphNodeSummary(null, null); + assertEquals("testGetGraphNodeSummary", "[Lifeline or Synchronous message or Synchronous message return or Asynchronous message or Asynchronous message return or Stop]", summary); + + } + + @SuppressWarnings("nls") + public void testMatches() { + Criteria criteria = new Criteria(); + criteria.setExpression("BALL_.*"); + + /* + * Note that method matches uses the Pattern class. Test + * only case sensitive/case insensitive case. All other regular + * expression cases are covered by Pattern class. + */ + + // case insensitive + assertTrue("testMatches", criteria.matches("BALL_REQUEST")); + assertTrue("testMatches", criteria.matches("BALL_REPLY")); + assertTrue("testMatches", criteria.matches("BALL_R")); + assertTrue("testMatches", criteria.matches("ball_request")); + assertTrue("testMatches", criteria.matches("ball_request")); + assertFalse("testMatches", criteria.matches("NOBALL_REQUEST")); + assertFalse("testMatches", criteria.matches("BLABLA")); + + // case sensitive + criteria.setCaseSenstiveSelected(true); + assertTrue("testMatches", criteria.matches("BALL_REQUEST")); + assertTrue("testMatches", criteria.matches("BALL_REPLY")); + assertTrue("testMatches", criteria.matches("BALL_R")); + assertFalse("testMatches", criteria.matches("ball_request")); + assertFalse("testMatches", criteria.matches("ball_request")); + assertFalse("testMatches", criteria.matches("NOBALL_REQUEST")); + assertFalse("testMatches", criteria.matches("BLABLA")); + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/AllTests.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/AllTests.java new file mode 100644 index 0000000000..cc660f28ca --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/AllTests.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + + public static Test suite() { + // Create instance of the test facility for the TmfUml2SD test + Uml2SDTestFacility.getInstance().init(true); + + TestSuite suite = new TestSuite(AllTests.class.getName()); + //$JUnit-BEGIN$ + suite.addTestSuite(TmfUml2SDSyncLoaderExpTest.class); + suite.addTestSuite(TmfUml2SDSyncLoaderPagesTest.class); + suite.addTestSuite(TmfUml2SDSyncLoaderTimeTest.class); + suite.addTestSuite(TmfUml2SDSyncLoaderSignalTest.class); + suite.addTestSuite(TmfUml2SDSyncLoaderFindTest.class); + suite.addTestSuite(TmfUml2SDSyncLoaderFilterTest.class); + //$JUnit-END$ + return suite; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/IUml2SDTestConstants.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/IUml2SDTestConstants.java new file mode 100644 index 0000000000..879e54f959 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/IUml2SDTestConstants.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl; + +/** + * Common constants for the TMF UML2SD test cases + */ +@SuppressWarnings("nls") +public interface IUml2SDTestConstants { + + final static public int WAIT_FOR_JOBS_DELAY = 1000; + final static public int GUI_REFESH_DELAY = 500; + final static public int INITIAL_INDEX_DELAY = 1000; + final static public int BROADCAST_DELAY = 2000; + + final static public int TOTAL_NUMBER_OF_PAGES = 9; + final static public int MAX_MESSEAGES_PER_PAGE = 10000; + final static public int NUM_MESSAGES_OF_LAST_PAGE = 32; + + final static public int DEFAULT_NUM_LIFELINES = 2; + final static public int NUM_OF_ALL_LIFELINES = 3; + final static public int PAGE_OF_ALL_LIFELINES = 4; + + final static public byte TIME_SCALE = -9; + + final static public String MASTER_PLAYER_NAME = "Master"; + final static public String FIRST_PLAYER_NAME = "player1"; + final static public String SECOND_PLAYER_NAME = "player2"; + +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/IUml2SdSignalValidator.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/IUml2SdSignalValidator.java new file mode 100644 index 0000000000..26294efb4a --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/IUml2SdSignalValidator.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl; + +import org.eclipse.linuxtools.tmf.event.TmfTimeRange; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; + +/** + * Interface for testing signal handling within TmfUml2SD + */ +public interface IUml2SdSignalValidator { + public boolean isSignalReceived(); + public void setSignalReceived(boolean received); + + public boolean isSourceError(); + public void setSourceError(boolean fIsSourceError); + + public boolean isCurrentTimeError(); + public void setCurrentTimeError(boolean fIsCurrentTimeError); + + public boolean isRangeError(); + public void setRangeError(boolean fIsRangeError); + + public boolean isSignalError(); + public void setSignalError(boolean fIsSignalError); + + public Object getSource(); + public void setSource(Object source); + + public TmfTimestamp getCurrentTime(); + public void setCurrentTime(TmfTimestamp currentTime); + + public TmfTimeRange getCurrentRange(); + public void setCurrentRange(TmfTimeRange currentRange); + +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderExpTest.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderExpTest.java new file mode 100644 index 0000000000..14fb9cf8d4 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderExpTest.java @@ -0,0 +1,181 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl; + +import java.util.List; + +import junit.framework.TestCase; + +import org.eclipse.linuxtools.tmf.signal.TmfSignalManager; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDAdvancedPagingProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDPagingProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.FilterCriteria; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.FilterListDialog; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl.TmfUml2SDSyncLoader; + +public class TmfUml2SDSyncLoaderExpTest extends TestCase { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + private Uml2SDTestFacility fFacility; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + public TmfUml2SDSyncLoaderExpTest() { + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + @Override + public void setUp() throws Exception { + super.setUp(); + fFacility = Uml2SDTestFacility.getInstance(); + fFacility.init(false); + // create filter criteria (incl. save) + fFacility.createFilterCriteria(); + fFacility.selectExperiment(); + } + + + @Override + public void tearDown() throws Exception { + fFacility.dispose(); + fFacility = null; + super.tearDown(); + } + + @SuppressWarnings("nls") + public void testExperimentHandling() { + + /* + * Test Case: 001 + * Description: Verify setup + * Verified Methods: loader.getTitleString() + * view.getPartName() + * view.getFrame() + * Expected result: Title, view name are set properly. + */ + + assertEquals("getTitleString", "Component Interactions", fFacility.getLoader().getTitleString()); + assertEquals("getPartName", "Sequence Diagram", fFacility.getSdView().getPartName()); + assertNotNull("getFrame", fFacility.getSdView().getFrame()); + + fFacility.disposeExperiment(); + + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + verifyPage(0, 0, false, false, 0); + + /* + * Test Case: 002 + * Description: Verifies cancellation of ongoing indexing. + * Verified Methods: loader.experimentSelected(), loader.experimentDisposed(), loader.nextPage() + * Expected result: No exceptions during cancellation and nextPage() operation.. + * + * Note this test is not sufficient to verify the concurrent access of the loader attributes + * by multiple threads. Contention might happen but it's not guaranteed. + */ + + for(int i = 0; i < 5; i++) { + fFacility.selectExperiment(false); + fFacility.delay(IUml2SDTestConstants.INITIAL_INDEX_DELAY); + + try { + fFacility.getExperiment().dispose(); + fFacility.getLoader().nextPage(); // to test out of bounce + // Note: To actually create an out of bound exception remove + // safety-checks in nextPage/moveToPage of loader class + } catch (Exception e){ + // No Exception expected + fail("exp.select/exp.dispose"); + } + } + + /* + * Test Case: 003 + * Description: Verifies disposed experiment. + * Verified Methods: loader.nextPage(), + * loader.pagesCount(), loader.hasNextPage(), loader.hasPrevPage(), + * frame.syncMessagesCount, frame.lifeLinesCount + * Expected result: Page values and filter are reset. + * + * Note this test is not sufficient to verify the concurrent access ofthe loader attributes + * by multiple threads. Contention might happen but it's not guaranteed. + */ + + verifyPage(0, 0, false, false, 0); + + // verify that all enable filters are disabled after disposal + List filter = FilterListDialog.getGlobalFilters(); + + for (FilterCriteria filterCriteria : filter) { + assertFalse("exp.dispose", filterCriteria.isActive()); + } + + /* + * Test Case: 004 + * Description: Verifies the disposal of the loader. + * Verified Methods: loader.dispose(), + * Expected result: All providers are removed from SDView. + */ + fFacility.getLoader().dispose(); + assertTrue("loader.dispose", fFacility.getSdView().getSDPagingProvider() == null); + assertTrue("loader.dispose", fFacility.getSdView().getSDFindProvider() == null); + assertTrue("loader.dispose", fFacility.getSdView().getSDFilterProvider() == null); + assertTrue("loader.dispose", fFacility.getSdView().getExtendedFindProvider() == null); + assertTrue("loader.dispose", fFacility.getSdView().getExtendedFilterProvider() == null); + + // Set again loader as signal handler, which was removed by the the dispose above + TmfSignalManager.register(fFacility.getLoader()); + + /* + * Test Case: 005 + * Description: Verifies setViewer. + * Verified Methods: loader.setViewer + * Expected result: Paging, find and filter provider are set + */ + + fFacility.getLoader().setViewer(fFacility.getSdView()); + ISDPagingProvider pagingProvider = fFacility.getSdView().getSDPagingProvider(); + assertTrue("loader.setViewer", pagingProvider != null); + assertTrue("loader.setViewer", pagingProvider instanceof ISDAdvancedPagingProvider); + assertTrue("loader.setViewer", pagingProvider instanceof TmfUml2SDSyncLoader); + + assertTrue("loader.setViewer", fFacility.getSdView().getSDFindProvider() != null); + + assertTrue("loader.setViewer", fFacility.getSdView().getSDFilterProvider() != null); + + // All other providers are not used. + assertTrue("loader.setViewer", fFacility.getSdView().getExtendedFindProvider() == null); + assertTrue("loader.setViewer", fFacility.getSdView().getExtendedFilterProvider() == null); + } + + @SuppressWarnings("nls") + private void verifyPage(int currentPage, int numMsg, boolean hasNext, boolean hasPrev, int lifelineCount) { + assertEquals("currentPage", currentPage, fFacility.getLoader().currentPage()); + assertEquals("syncMessageCount, ", numMsg, fFacility.getSdView().getFrame().syncMessageCount()); + if (hasNext) { + assertTrue("hasNextpage", fFacility.getLoader().hasNextPage()); + } else { + assertFalse("hasNextPage", fFacility.getLoader().hasNextPage()); + } + if (hasPrev) { + assertTrue("hasPrevPage", fFacility.getLoader().hasPrevPage()); + } else { + assertFalse("hasPrevPage", fFacility.getLoader().hasPrevPage()); + } + assertEquals("lifeLinesCount", lifelineCount, fFacility.getSdView().getFrame().lifeLinesCount()); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderFilterTest.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderFilterTest.java new file mode 100644 index 0000000000..f6be7a0145 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderFilterTest.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SyncMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.Criteria; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.FilterCriteria; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl.TmfSyncMessage; + +public class TmfUml2SDSyncLoaderFilterTest extends TestCase { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + private Uml2SDTestFacility fFacility; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public TmfUml2SDSyncLoaderFilterTest() { + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + @Override + public void setUp() throws Exception { + super.setUp(); + fFacility = Uml2SDTestFacility.getInstance(); + fFacility.init(false); + fFacility.selectExperiment(); + } + + + @Override + public void tearDown() throws Exception { + fFacility.disposeExperiment(); + fFacility.dispose(); + fFacility = null; + super.tearDown(); + } + + @SuppressWarnings("nls") + public void testFilterHandling() { + + // Create Filter Criteria + List filterToSave = new ArrayList(); + Criteria criteria = new Criteria(); + criteria.setLifeLineSelected(true); + criteria.setExpression(IUml2SDTestConstants.FIRST_PLAYER_NAME); + filterToSave.add(new FilterCriteria(criteria, true, false)); + + criteria = new Criteria(); + criteria.setLifeLineSelected(true); + criteria.setExpression(IUml2SDTestConstants.MASTER_PLAYER_NAME); + filterToSave.add(new FilterCriteria(criteria, false, false)); + + criteria = new Criteria(); + criteria.setSyncMessageSelected(true); + criteria.setExpression("BALL_.*"); //$NON-NLS-1$ + filterToSave.add(new FilterCriteria(criteria, false, false)); + + /* + * Test Case: 001 + * Description: Verify the filter lifelines (1 out of 2 is hidden) + * Verified Methods: loader.filter() + * Expected result: Only one lifeline is visible with no messages + */ + fFacility.getLoader().filter(filterToSave); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + + assertEquals("filter", 1, fFacility.getSdView().getFrame().lifeLinesCount()); + assertEquals("filter", IUml2SDTestConstants.MASTER_PLAYER_NAME, fFacility.getSdView().getFrame().getLifeline(0).getName()); + assertEquals("filter", 0, fFacility.getSdView().getFrame().syncMessageCount()); + + + /* + * Test Case: 002 + * Description: Verify the filter lifelines (2 out of 2 are hidden) + * Verified Methods: loader.filter(), loader.fillCurrentPage() + * Expected result: Neiter liflines nor messages are visible + */ + filterToSave.get(1).setActive(true); + fFacility.getLoader().filter(filterToSave); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + + assertEquals("filter", 0, fFacility.getSdView().getFrame().lifeLinesCount()); + assertEquals("filter", 0, fFacility.getSdView().getFrame().syncMessageCount()); + + /* + * Test Case: 003 + * Description: Verify removal of all filters + * Verified Methods: loader.filter(), loader.fillCurrentPage() + * Expected result: Everything is shown + */ + filterToSave.get(0).setActive(false); + filterToSave.get(1).setActive(false); + fFacility.getLoader().filter(filterToSave); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + + assertEquals("filter", 2, fFacility.getSdView().getFrame().lifeLinesCount()); + assertEquals("filter", IUml2SDTestConstants.MAX_MESSEAGES_PER_PAGE, fFacility.getSdView().getFrame().syncMessageCount()); + + /* + * Test Case: 004 + * Description: Verify filter of messages + * Verified Methods: loader.filter(), loader.fillCurrentPage() + * Expected result: Only particular messages are shown + */ + filterToSave.get(2).setActive(true); + fFacility.getLoader().filter(filterToSave); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + + assertEquals("filter", 2, fFacility.getSdView().getFrame().lifeLinesCount()); + assertEquals("filter", 6, fFacility.getSdView().getFrame().syncMessageCount()); + + String messages[] = { "REGISTER_PLAYER_REQUEST", "REGISTER_PLAYER_REPLY", "GAME_REQUEST", "GAME_REPLY", "START_GAME_REQUEST", "START_GAME_REPLY" }; + + for (int i = 0; i < messages.length; i++) { + SyncMessage msg = fFacility.getSdView().getFrame().getSyncMessage(i); + assertTrue("filter", msg instanceof TmfSyncMessage); + assertEquals("filter", messages[i], msg.getName()); + } + + /* + * Test Case: 005 + * Description: Verify filter lifeline (1 out of three lifelines). + * Note that filter was set during change of page. + * Verified Methods: loader.filter(), loader.fillCurrentPage() + * Expected result: Only 2 lifelines and their interactions are shown + */ + filterToSave.get(0).setActive(true); + filterToSave.get(2).setActive(false); + fFacility.setPage(IUml2SDTestConstants.PAGE_OF_ALL_LIFELINES); + + assertEquals("filter", 2, fFacility.getSdView().getFrame().lifeLinesCount()); + String lifelines[] = { IUml2SDTestConstants.MASTER_PLAYER_NAME, IUml2SDTestConstants.SECOND_PLAYER_NAME }; + + for (int i = 0; i < lifelines.length; i++) { + Lifeline line = fFacility.getSdView().getFrame().getLifeline(i); + assertEquals("filter", lifelines[i], line.getName()); + } + + assertTrue(fFacility.getSdView().getFrame().syncMessageCount() > 0); + + filterToSave.get(2).setActive(false); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderFindTest.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderFindTest.java new file mode 100644 index 0000000000..245596ca68 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderFindTest.java @@ -0,0 +1,292 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl; + +import java.util.List; + +import junit.framework.TestCase; + +import org.eclipse.linuxtools.tmf.event.TmfTimeRange; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDGraphNodeSupporter; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.Criteria; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl.TmfSyncMessage; + +public class TmfUml2SDSyncLoaderFindTest extends TestCase { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + + // Test case 002 expected values + final static private Uml2SDTestTimestamp TC_002_TIME_VALUE = new Uml2SDTestTimestamp(9788642104149L); + final static private String TC_002_MESSAGE_NAME = "GAME_REQUEST"; //$NON-NLS-1$ + final static private int TC_002_PAGE_VALUE = 0; + final static private int TC_002_START_OCCURRANCE = 3; + final static private int TC_002_END_OCCURRANCE = TC_002_START_OCCURRANCE; + final static private String TC_002_START_LIFELINE = IUml2SDTestConstants.FIRST_PLAYER_NAME; + final static private String TC_002_END_LIFELINE = IUml2SDTestConstants.MASTER_PLAYER_NAME; + + // Test case 003 expected values + final static private Uml2SDTestTimestamp TC_003_TIME_VALUE = new Uml2SDTestTimestamp(9788642113228L); + final static private String TC_003_MESSAGE_NAME = "GAME_REPLY"; //$NON-NLS-1$ + final static private int TC_003_PAGE_VALUE = 0; + final static private int TC_003_START_OCCURRANCE = 4; + final static private int TC_003_END_OCCURRANCE = TC_003_START_OCCURRANCE; + final static private String TC_003_START_LIFELINE = IUml2SDTestConstants.MASTER_PLAYER_NAME; + final static private String TC_003_END_LIFELINE = IUml2SDTestConstants.FIRST_PLAYER_NAME; + + // Test case 004 expected values + final static private Uml2SDTestTimestamp TC_004_TIME_VALUE = new Uml2SDTestTimestamp(9791893030834L); + final static private String TC_004_MESSAGE_NAME = "GAME_REQUEST"; //$NON-NLS-1$ + final static private int TC_004_PAGE_VALUE = 4; + final static private int TC_004_START_OCCURRANCE = 19; + final static private int TC_004_END_OCCURRANCE = TC_004_START_OCCURRANCE; + final static private String TC_004_START_LIFELINE = IUml2SDTestConstants.SECOND_PLAYER_NAME; + final static private String TC_004_END_LIFELINE = IUml2SDTestConstants.MASTER_PLAYER_NAME; + + // Test case 005 expected values + final static private int TC_005_PAGE_VALUE = 0; + final static private String TC_005_LIFELINE_NAME = IUml2SDTestConstants.FIRST_PLAYER_NAME; + + // Test case 006 expected values + final static private int TC_006_PAGE_VALUE = 4; + final static private String TC_006_LIFELINE_NAME = IUml2SDTestConstants.SECOND_PLAYER_NAME; + + private Uml2SDTestFacility fFacility; + private Uml2SDSignalValidator fTmfComponent; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public TmfUml2SDSyncLoaderFindTest() { + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + @Override + public void setUp() throws Exception { + super.setUp(); + fFacility = Uml2SDTestFacility.getInstance(); + fFacility.init(false); + fFacility.selectExperiment(); + } + + @Override + public void tearDown() throws Exception { + fFacility.disposeExperiment(); + fFacility.dispose(); + fFacility = null; + super.tearDown(); + } + + @SuppressWarnings("nls") + public void testFindHandling() { + fTmfComponent = new Uml2SDSignalValidator(); + + /* + * Test Case: 001 + * Description: Verify the ISDGraphNodeSupporter implementation + * Verified Methods: loader.isNodeSupported(), loader.getNodeName() + * Expected result: Correct values are returned, i.e. only lifelines and sync. messages are supported + */ + assertTrue("isNodeSupported", fFacility.getLoader().isNodeSupported(ISDGraphNodeSupporter.LIFELINE)); + assertTrue("isNodeSupported", fFacility.getLoader().isNodeSupported(ISDGraphNodeSupporter.SYNCMESSAGE)); + assertFalse("isNodeSupported", fFacility.getLoader().isNodeSupported(ISDGraphNodeSupporter.SYNCMESSAGERETURN)); + assertFalse("isNodeSupported", fFacility.getLoader().isNodeSupported(ISDGraphNodeSupporter.ASYNCMESSAGE)); + assertFalse("isNodeSupported", fFacility.getLoader().isNodeSupported(ISDGraphNodeSupporter.ASYNCMESSAGERETURN)); + assertFalse("isNodeSupported", fFacility.getLoader().isNodeSupported(ISDGraphNodeSupporter.STOP)); + + assertEquals("getNodeName", "Lifeline", fFacility.getLoader().getNodeName(ISDGraphNodeSupporter.LIFELINE, null)); + assertEquals("getNodeName", "Interaction", fFacility.getLoader().getNodeName(ISDGraphNodeSupporter.SYNCMESSAGE, null)); + assertEquals("getNodeName", "", fFacility.getLoader().getNodeName(ISDGraphNodeSupporter.SYNCMESSAGERETURN, null)); + assertEquals("getNodeName", "", fFacility.getLoader().getNodeName(ISDGraphNodeSupporter.ASYNCMESSAGE, null)); + assertEquals("getNodeName", "", fFacility.getLoader().getNodeName(ISDGraphNodeSupporter.ASYNCMESSAGERETURN, null)); + assertEquals("getNodeName", "", fFacility.getLoader().getNodeName(ISDGraphNodeSupporter.STOP, null)); + + fFacility.getLoader().cancel(); + + /* + * Test Case: 002 + * Description: Verify 1st message find within page + * Verified Methods: loader.find(), loader.moveToMessage() + * Expected result: Correct message is selected + */ + Criteria criteria = new Criteria(); + criteria.setSyncMessageSelected(true); + criteria.setExpression("GAME_.*"); + + // set expected values + fTmfComponent.setSource(fFacility.getLoader()); + fTmfComponent.setCurrentTime(TC_002_TIME_VALUE); + fTmfComponent.setCurrentRange(null); // not used + fTmfComponent.setSignalReceived(false); + + fFacility.getLoader().find(criteria); + assertTrue("find", fTmfComponent.isSignalReceived()); + assertFalse("find", fTmfComponent.isSignalError()); + assertFalse("find", fTmfComponent.isCurrentTimeError()); + assertFalse("find", fTmfComponent.isSourceError()); + + assertEquals("find", TC_002_PAGE_VALUE, fFacility.getLoader().currentPage()); + List selection = fFacility.getSdView().getSDWidget().getSelection(); + assertNotNull(selection); + assertEquals("find", 1, selection.size()); + assertTrue(selection.get(0) instanceof TmfSyncMessage); + TmfSyncMessage msg = (TmfSyncMessage) selection.get(0); + assertEquals("find", TC_002_MESSAGE_NAME, msg.getName()); + assertEquals("find", 0, TC_002_TIME_VALUE.compareTo(msg.getStartTime(), false)); + assertEquals("find", TC_002_START_OCCURRANCE, msg.getStartOccurrence()); + assertEquals("find", TC_002_END_OCCURRANCE, msg.getEndOccurrence()); + assertEquals("find", TC_002_START_LIFELINE, msg.getStartLifeline().getName()); + assertEquals("find", TC_002_END_LIFELINE, msg.getEndLifeline().getName()); + + /* + * Test Case: 003 + * Description: Verify 2nd message find within page + * Verified Methods: loader.find(), loader.moveToMessage() + * Expected result: Correct message is selected + */ + // set expected values + fTmfComponent.setSource(fFacility.getLoader()); + fTmfComponent.setCurrentTime(TC_003_TIME_VALUE); + fTmfComponent.setCurrentRange(null); // not used + + fTmfComponent.setSignalReceived(false); + + fFacility.getLoader().find(criteria); + assertTrue("find", fTmfComponent.isSignalReceived()); + assertFalse("find", fTmfComponent.isSignalError()); + assertFalse("find", fTmfComponent.isCurrentTimeError()); + assertFalse("find", fTmfComponent.isSourceError()); + + assertEquals("find", TC_003_PAGE_VALUE, fFacility.getLoader().currentPage()); + selection = fFacility.getSdView().getSDWidget().getSelection(); + assertNotNull(selection); + assertEquals("find", 1, selection.size()); + assertTrue(selection.get(0) instanceof TmfSyncMessage); + msg = (TmfSyncMessage) selection.get(0); + assertEquals("find", TC_003_MESSAGE_NAME, msg.getName()); + assertEquals("find", 0, TC_003_TIME_VALUE.compareTo(msg.getStartTime(), false)); + assertEquals("find", TC_003_START_OCCURRANCE, msg.getStartOccurrence()); + assertEquals("find", TC_003_END_OCCURRANCE, msg.getEndOccurrence()); + assertEquals("find", TC_003_START_LIFELINE, msg.getStartLifeline().getName()); + assertEquals("find", TC_003_END_LIFELINE, msg.getEndLifeline().getName()); + + /* + * Test Case: 004 + * Description: Verify 1st message across page + * Verified Methods: loader.find(), loader.moveToPage(), loader.moveToMessage() + * Expected result: Correct message is selected + */ + // set expected values + fTmfComponent.setSource(fFacility.getLoader()); + fTmfComponent.setCurrentTime(TC_004_TIME_VALUE); + fTmfComponent.setCurrentRange(new TmfTimeRange(TmfTimestamp.BigBang, TmfTimestamp.BigCrunch)); // not used + + fTmfComponent.setSignalReceived(false); + + fFacility.getLoader().find(criteria); + fFacility.waitForJobs(); // find across pages uses a job + fFacility.getLoader().waitForCompletion(); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + + assertTrue("find", fTmfComponent.isSignalReceived()); + assertFalse("find", fTmfComponent.isSignalError()); + assertFalse("find", fTmfComponent.isCurrentTimeError()); + assertFalse("find", fTmfComponent.isSourceError()); + + assertEquals("find", TC_004_PAGE_VALUE, fFacility.getLoader().currentPage()); + selection = fFacility.getSdView().getSDWidget().getSelection(); + assertNotNull(selection); + assertEquals("find", 1, selection.size()); + assertTrue(selection.get(0) instanceof TmfSyncMessage); + msg = (TmfSyncMessage) selection.get(0); + assertEquals("find", TC_004_MESSAGE_NAME, msg.getName()); + assertEquals("find", 0, TC_004_TIME_VALUE.compareTo(msg.getStartTime(), false)); + assertEquals("find", TC_004_START_OCCURRANCE, msg.getStartOccurrence()); + assertEquals("find", TC_004_END_OCCURRANCE, msg.getEndOccurrence()); + assertEquals("find", TC_004_START_LIFELINE, msg.getStartLifeline().getName()); + assertEquals("find", TC_004_END_LIFELINE, msg.getEndLifeline().getName()); + + // cancel find and go back to first page + fFacility.getLoader().cancel(); + fFacility.firstPage(); + + /* + * Test Case: 005 + * Description: Verify find of lifeline within page + * Verified Methods: loader.find(), loader.moveToPage(), loader.moveToMessage() + * Expected result: Correct message is selected + */ + criteria = new Criteria(); + criteria.setLifeLineSelected(true); + criteria.setExpression(IUml2SDTestConstants.FIRST_PLAYER_NAME); + fFacility.getLoader().find(criteria); + + assertEquals("find", TC_005_PAGE_VALUE, fFacility.getLoader().currentPage()); + selection = fFacility.getSdView().getSDWidget().getSelection(); + assertNotNull(selection); + assertEquals("find", 1, selection.size()); + assertTrue(selection.get(0) instanceof Lifeline); + Lifeline lifeline = (Lifeline) selection.get(0); + assertEquals("find", TC_005_LIFELINE_NAME, lifeline.getName()); + + /* + * Test Case: 006 + * Description: Verify lifeline across page + * Verified Methods: loader.find(), loader.moveToPage(), loader.moveToMessage() + * Expected result: Correct message is selected + */ + criteria = new Criteria(); + criteria.setLifeLineSelected(true); + criteria.setExpression(IUml2SDTestConstants.SECOND_PLAYER_NAME); + + fFacility.getLoader().find(criteria); + fFacility.waitForJobs(); // find across pages uses a job + fFacility.getLoader().waitForCompletion(); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + + assertEquals("find", TC_006_PAGE_VALUE, fFacility.getLoader().currentPage()); + selection = fFacility.getSdView().getSDWidget().getSelection(); + assertNotNull(selection); + assertEquals("find", 1, selection.size()); + assertTrue(selection.get(0) instanceof Lifeline); + lifeline = (Lifeline) selection.get(0); + assertEquals("find", TC_006_LIFELINE_NAME, lifeline.getName()); + + + // cancel find and go back to first page + fFacility.getLoader().cancel(); + fFacility.firstPage(); + + /* + * Test Case: 007 + * Description: Verify cancel ongoing search job + * Verified Methods: loader.find(), loader.find() + * Expected result: Cancelled find + */ + criteria = new Criteria(); + criteria.setLifeLineSelected(true); + criteria.setExpression(IUml2SDTestConstants.SECOND_PLAYER_NAME); + + fFacility.getLoader().find(criteria); + fFacility.delay(200); // to make sure job was started + fFacility.getLoader().cancel(); + + assertEquals("find", 0, fFacility.getLoader().currentPage()); // we are still at the first page + fTmfComponent.dispose(); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderPagesTest.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderPagesTest.java new file mode 100644 index 0000000000..80b088ada7 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderPagesTest.java @@ -0,0 +1,215 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl; + +import junit.framework.TestCase; + +public class TmfUml2SDSyncLoaderPagesTest extends TestCase { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + private Uml2SDTestFacility fFacility; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public TmfUml2SDSyncLoaderPagesTest() { + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + @Override + public void setUp() throws Exception { + super.setUp(); + fFacility = Uml2SDTestFacility.getInstance(); + fFacility.init(false); + fFacility.selectExperiment(); + } + + + @Override + public void tearDown() throws Exception { + fFacility.disposeExperiment(); + fFacility.dispose(); + fFacility = null; + super.tearDown(); + } + + public void testPageHandling() { + + verifyPagesCount(); + verifyNextPage(); + verifyPrevPage(); + verifyFirstPage(); + verifyLastPage(); + verifyPageNumberChanged(); + + } + + private void verifyPagesCount() { + + /* + * Test Case: 001 + * Description: Test number of pages. + * Verified Methods: loader.pagesCount(). + * Expected result: ITestConstants.TOTAL_NUMBER_OF_PAGES of pages + */ + assertEquals(IUml2SDTestConstants.TOTAL_NUMBER_OF_PAGES, fFacility.getLoader().pagesCount()); + } + + private void verifyNextPage() { + // assuming we are at the first page + + /* + * Test Case: 002 + * Description: Tests next page feature. + * Verified Methods: loader.nextPage(), loader.fillCurrentPage(), loader.pagesCount(), + * loader.hasNextPage(), loader.hasPrevPage(), + * frame.syncMessagesCount, frame.lifeLinesCount + * Expected result: Expected values are return. + */ + + for(int i = 0; i < IUml2SDTestConstants.TOTAL_NUMBER_OF_PAGES-2; i++) { + fFacility.nextPage(); + + if (i+1 == IUml2SDTestConstants.PAGE_OF_ALL_LIFELINES) { + verifyPage(i+1, IUml2SDTestConstants.MAX_MESSEAGES_PER_PAGE, true, true, IUml2SDTestConstants.NUM_OF_ALL_LIFELINES); + } + else { + verifyPage(i+1, IUml2SDTestConstants.MAX_MESSEAGES_PER_PAGE, true, true); + } + } + + // Last Page + fFacility.nextPage(); + verifyPage(IUml2SDTestConstants.TOTAL_NUMBER_OF_PAGES - 1, IUml2SDTestConstants.NUM_MESSAGES_OF_LAST_PAGE, false, true); + + // Check for index out of bounce + try { + fFacility.getLoader().nextPage(); + } catch (Exception e) { + fail(); + } + + fFacility.firstPage(); + } + + + private void verifyPrevPage() { + // assuming we are at the first page + + /* + * Test Case: 003 + * Description: Test previous page feature. + * Verified Methods: loader.prevPage(), loader.fillCurrentPage(), loader.pagesCount(), + * loader.hasNextPage(), loader.hasPrevPage(), + * frame.syncMessagesCount, frame.lifeLinesCount + * Expected result: Expected values are return. + */ + + // Last Page + fFacility.lastPage(); + assertEquals(IUml2SDTestConstants.TOTAL_NUMBER_OF_PAGES - 1, fFacility.getLoader().currentPage()); + assertEquals(IUml2SDTestConstants.NUM_MESSAGES_OF_LAST_PAGE, fFacility.getSdView().getFrame().syncMessageCount()); + assertFalse(fFacility.getLoader().hasNextPage()); + assertTrue(fFacility.getLoader().hasPrevPage()); + assertEquals(2, fFacility.getSdView().getFrame().lifeLinesCount()); + + for(int i = IUml2SDTestConstants.TOTAL_NUMBER_OF_PAGES-2; i > 0; i--) { + fFacility.prevPage(); + if (i == IUml2SDTestConstants.PAGE_OF_ALL_LIFELINES) + verifyPage(i, IUml2SDTestConstants.MAX_MESSEAGES_PER_PAGE, true, true, IUml2SDTestConstants.NUM_OF_ALL_LIFELINES); + else + verifyPage(i, IUml2SDTestConstants.MAX_MESSEAGES_PER_PAGE, true, true); + } + + fFacility.prevPage(); + verifyPage(0, IUml2SDTestConstants.MAX_MESSEAGES_PER_PAGE, true, false); + + // Check for index out of bounce + try { + fFacility.getLoader().prevPage(); + } catch (Exception e) { + fail(); + } + } + + private void verifyFirstPage() { + fFacility.lastPage(); + /* + * Test Case: 004 + * Description: Test first page feature. + * Verified Methods: loader.firstPage(), loader.fillCurrentPage(), loader.pagesCount(), + * loader.hasNextPage(), loader.hasPrevPage(), + * frame.syncMessagesCount, frame.lifeLinesCount + * Expected result: Expected values are return. + */ + + // First Page + fFacility.firstPage(); + verifyPage(0, IUml2SDTestConstants.MAX_MESSEAGES_PER_PAGE, true, false); + } + + + private void verifyLastPage() { + /* + * Test Case: 005 + * Description: Test last page feature. + * Verified Methods: loader.lastPage(), loader.pagesCount(), loader.hasNextPage(), loader.hasPrevPage() + * frame.syncMessagesCount, frame.lifeLinesCount + * Expected result: Expected values are return. + */ + fFacility.lastPage(); + verifyPage(IUml2SDTestConstants.TOTAL_NUMBER_OF_PAGES - 1, IUml2SDTestConstants.NUM_MESSAGES_OF_LAST_PAGE, false, true); + fFacility.firstPage(); + } + + private void verifyPageNumberChanged() { + + /* + * Test Case: 006 + * Description: Test move to any page feature. + * Verified Methods: loader.pageNumberChanged(), loader.fillCurrentPage(), loader.pagesCount(), + * loader.hasNextPage(), loader.hasPrevPage(), + * frame.syncMessagesCount, frame.lifeLinesCount + * Expected result: Expected values are return. + */ + + // any page + fFacility.setPage(IUml2SDTestConstants.PAGE_OF_ALL_LIFELINES); + verifyPage(IUml2SDTestConstants.PAGE_OF_ALL_LIFELINES, IUml2SDTestConstants.MAX_MESSEAGES_PER_PAGE, true, true, IUml2SDTestConstants.NUM_OF_ALL_LIFELINES); + fFacility.firstPage(); + } + + private void verifyPage(int currentPage, int numMsg, boolean hasNext, boolean hasPrev) { + verifyPage(currentPage, numMsg, hasNext, hasPrev, IUml2SDTestConstants.DEFAULT_NUM_LIFELINES); + } + + @SuppressWarnings("nls") + private void verifyPage(int currentPage, int numMsg, boolean hasNext, boolean hasPrev, int lifelineCount) { + assertEquals("currentPage", currentPage, fFacility.getLoader().currentPage()); + assertEquals("syncMessageCount, ", numMsg, fFacility.getSdView().getFrame().syncMessageCount()); + if (hasNext) { + assertTrue("hasNextpage", fFacility.getLoader().hasNextPage()); + } else { + assertFalse("hasNextPage", fFacility.getLoader().hasNextPage()); + } + if (hasPrev) { + assertTrue("hasPrevPage", fFacility.getLoader().hasPrevPage()); + } else { + assertFalse("hasPrevPage", fFacility.getLoader().hasPrevPage()); + } + assertEquals("lifeLinesCount", lifelineCount, fFacility.getSdView().getFrame().lifeLinesCount()); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderSignalTest.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderSignalTest.java new file mode 100644 index 0000000000..185ba5f3ac --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderSignalTest.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl; + +import junit.framework.TestCase; + +import org.eclipse.linuxtools.tmf.event.TmfTimeRange; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.signal.TmfRangeSynchSignal; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; + +public class TmfUml2SDSyncLoaderSignalTest extends TestCase { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + private Uml2SDTestFacility fFacility; + private Uml2SDSignalValidator fTmfComponent; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public TmfUml2SDSyncLoaderSignalTest() { + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + @Override + public void setUp() throws Exception { + super.setUp(); + fFacility = Uml2SDTestFacility.getInstance(); + fFacility.init(false); + fFacility.selectExperiment(); + } + + @Override + public void tearDown() throws Exception { + fFacility.disposeExperiment(); + fFacility.dispose(); + fFacility = null; + super.tearDown(); + } + + @SuppressWarnings("nls") + public void testSignalHandling() { + + TmfTimeRange range = new TmfTimeRange(new Uml2SDTestTimestamp(9789689220871L), new Uml2SDTestTimestamp(9789773881426L)); + // Get range window for tests below + TmfTimestamp rangeWindow = range.getEndTime().getDelta(range.getStartTime()); + TmfTimestamp currentTime = new Uml2SDTestTimestamp(9789773782043L); + + fFacility.getTrace().broadcast(new TmfRangeSynchSignal(this, range, currentTime)); + fFacility.delay(IUml2SDTestConstants.BROADCAST_DELAY); + + fTmfComponent = new Uml2SDSignalValidator(); + + /* + * Test Case: 001 + * Description: Verify that time range signal is send with correct values when going to first page + * Verified Methods: broadcast() + * Expected result: Time range sync signal is sent with correct range and current time. + */ + currentTime = new Uml2SDTestTimestamp(9788641608418L); + range = new TmfTimeRange(currentTime, new Uml2SDTestTimestamp(currentTime.getValue() + rangeWindow.getValue())); + + fTmfComponent.setSignalError(false); + fTmfComponent.setSignalReceived(false); + fTmfComponent.setCurrentTimeError(false); + fTmfComponent.setRangeError(false); + fTmfComponent.setSourceError(false); + + // set expected values + fTmfComponent.setSource(fFacility.getLoader()); + fTmfComponent.setCurrentTime(currentTime); + fTmfComponent.setCurrentRange(range); + + fFacility.firstPage(); + assertTrue("TmfRangeSynchSignal", fTmfComponent.isSignalReceived()); + assertFalse("TmfRangeSynchSignal", fTmfComponent.isSignalError()); + assertFalse("TmfRangeSynchSignal", fTmfComponent.isCurrentTimeError()); + assertFalse("TmfRangeSynchSignal", fTmfComponent.isSourceError()); + assertFalse("TmfRangeSynchSignal", fTmfComponent.isRangeError()); + + /* + * Test Case: 002 + * Description: Verify that time sync signal is sent correctly after selection + * Verified Methods: loader.broadcast(), testSelectionChanged + * Expected result: Time sync signal is sent with correct current time. + */ + fTmfComponent.setSignalReceived(false); + + int count = fFacility.getSdView().getFrame().syncMessageCount(); + assertEquals("Test Preparation", IUml2SDTestConstants.MAX_MESSEAGES_PER_PAGE, count); + GraphNode node = fFacility.getSdView().getFrame().getSyncMessage(3); + + // set expected values + fTmfComponent.setSource(fFacility.getLoader()); + fTmfComponent.setCurrentTime(new Uml2SDTestTimestamp(9788642113228L)); + fTmfComponent.setCurrentRange(null); // not used + + fFacility.getSdView().getSDWidget().moveTo(node); // selects the given node + assertTrue("TmfTimeSynchSignal", fTmfComponent.isSignalReceived()); + assertFalse("TmfTimeSynchSignal", fTmfComponent.isSignalError()); + assertFalse("TmfTimeSynchSignal", fTmfComponent.isCurrentTimeError()); + assertFalse("TmfTimeSynchSignal", fTmfComponent.isSourceError()); + + fTmfComponent.setSignalReceived(false); + + fTmfComponent.dispose(); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderTimeTest.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderTimeTest.java new file mode 100644 index 0000000000..b4729111ad --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/TmfUml2SDSyncLoaderTimeTest.java @@ -0,0 +1,275 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl; + +import java.util.List; + +import junit.framework.TestCase; + +import org.eclipse.linuxtools.tmf.event.TmfTimeRange; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.signal.TmfRangeSynchSignal; +import org.eclipse.linuxtools.tmf.signal.TmfTimeSynchSignal; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl.TmfSyncMessage; + +public class TmfUml2SDSyncLoaderTimeTest extends TestCase { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + // Test case 001 expected values + final static private Uml2SDTestTimestamp TC_001_TIME_VALUE = new Uml2SDTestTimestamp(9788642228395L); + final static private String TC_001_MESSAGE_NAME = "START_GAME_REPLY"; //$NON-NLS-1$ + final static private int TC_001_PAGE_VALUE = 0; + final static private int TC_001_START_OCCURRANCE = 6; + final static private int TC_001_END_OCCURRANCE = TC_001_START_OCCURRANCE; + final static private String TC_001_START_LIFELINE = IUml2SDTestConstants.MASTER_PLAYER_NAME; + final static private String TC_001_END_LIFELINE = IUml2SDTestConstants.FIRST_PLAYER_NAME; + + // Test case 002 expected values + final static private Uml2SDTestTimestamp TC_002_TIME_VALUE = new Uml2SDTestTimestamp(9789689830722L); + final static private String TC_002_MESSAGE_NAME = "PAUSE_GAME_REQUEST"; //$NON-NLS-1$ + final static private int TC_002_PAGE_VALUE = 2; + final static private int TC_002_START_OCCURRANCE = 7; + final static private int TC_002_END_OCCURRANCE = TC_002_START_OCCURRANCE; + final static private String TC_002_START_LIFELINE = IUml2SDTestConstants.FIRST_PLAYER_NAME; + final static private String TC_002_END_LIFELINE = IUml2SDTestConstants.MASTER_PLAYER_NAME; + + // Test case 003 expected values + final static private Uml2SDTestTimestamp TC_003_TIME_VALUE = new Uml2SDTestTimestamp(9790750000000L); + final static private int TC_003_PAGE_VALUE = 4; + + // Test case 004 expected values + final static private int TC_004_PAGE_VALUE = 0; + + // Test case 005 expected values + final static private int TC_005_PAGE_VALUE = IUml2SDTestConstants.TOTAL_NUMBER_OF_PAGES - 1; + + // Test case 006 expected values + final static private Uml2SDTestTimestamp TC_006_TIME_VALUE = new Uml2SDTestTimestamp(9792420661655L); + final static private int TC_006_PAGE_VALUE = 4; + final static private int TC_006_START_OCCURRANCE = IUml2SDTestConstants.MAX_MESSEAGES_PER_PAGE; + final static private int TC_006_END_OCCURRANCE = TC_006_START_OCCURRANCE; + + // Test case 007 expected values + final static private Uml2SDTestTimestamp TC_007_TIME_VALUE = new Uml2SDTestTimestamp(9792420756010L); + final static private int TC_007_PAGE_VALUE = 5; + final static private int TC_007_START_OCCURRANCE = 1; + final static private int TC_007_END_OCCURRANCE = TC_007_START_OCCURRANCE; + + // Test case 008 expected values + final static private Uml2SDTestTimestamp TC_008_TIME_VALUE = new Uml2SDTestTimestamp(9788642228395L); + final static private int TC_008_PAGE_VALUE = 0; + final static private Uml2SDTestTimestamp TC_008_START_TIME_VALUE = new Uml2SDTestTimestamp(9788642228395L); + final static private Uml2SDTestTimestamp TC_008_END_TIME_VALUE = new Uml2SDTestTimestamp(9789164833324L); + + // Test case 009 expected values + final static private Uml2SDTestTimestamp TC_009_TIME_VALUE = new Uml2SDTestTimestamp(9789773782043L); + final static private int TC_009_PAGE_VALUE = 2; + final static private Uml2SDTestTimestamp TC_009_START_TIME_VALUE = new Uml2SDTestTimestamp(9789689220871L); + final static private Uml2SDTestTimestamp TC_009_END_TIME_VALUE = new Uml2SDTestTimestamp(9789773881426L); + + private Uml2SDTestFacility fFacility; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public TmfUml2SDSyncLoaderTimeTest() { + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + @Override + public void setUp() throws Exception { + super.setUp(); + fFacility = Uml2SDTestFacility.getInstance(); + fFacility.init(false); + fFacility.selectExperiment(); + } + + + @Override + public void tearDown() throws Exception { + fFacility.disposeExperiment(); + fFacility.dispose(); + fFacility = null; + super.tearDown(); + } + + @SuppressWarnings("nls") + public void testTimeHandling() { + /* + * Test Case: 001 + * Description: Verify synchToTime (exact time in page), selection of message in page + * Verified Methods: loader.syncToTime(), loader.moveToMessage(), loader.moveToMessageInPage() + * Expected result: Correct message is selected. + */ + fFacility.getTrace().broadcast(new TmfTimeSynchSignal(this, TC_001_TIME_VALUE)); + fFacility.getLoader().waitForCompletion(); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + assertEquals("synchToTime", TC_001_PAGE_VALUE, fFacility.getLoader().currentPage()); + List selection = fFacility.getSdView().getSDWidget().getSelection(); + assertNotNull(selection); + assertEquals("synchToTime", 1, selection.size()); + assertTrue(selection.get(0) instanceof TmfSyncMessage); + TmfSyncMessage msg = (TmfSyncMessage) selection.get(0); + assertEquals("synchToTime", TC_001_MESSAGE_NAME, msg.getName()); + assertEquals("synchToTime", 0, TC_001_TIME_VALUE.compareTo(msg.getStartTime(), false)); + assertEquals("synchToTime", TC_001_START_OCCURRANCE, msg.getStartOccurrence()); + assertEquals("synchToTime", TC_001_END_OCCURRANCE, msg.getEndOccurrence()); + assertEquals("synchToTime", TC_001_START_LIFELINE, msg.getStartLifeline().getName()); + assertEquals("synchToTime", TC_001_END_LIFELINE, msg.getEndLifeline().getName()); + + /* + * Test Case: 002 + * Description: Verify synchToTime (exact time outside of page), selection of message in page + * Verified Methods: loader.syncToTime(), loader.moveToMessage(), loader.moveToPage() + * Expected result: Correct message is selected. + */ + fFacility.getTrace().broadcast(new TmfTimeSynchSignal(this, TC_002_TIME_VALUE)); + fFacility.getLoader().waitForCompletion(); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + assertEquals("synchToTime", TC_002_PAGE_VALUE, fFacility.getLoader().currentPage()); + selection = fFacility.getSdView().getSDWidget().getSelection(); + assertNotNull("synchToTime", selection); + assertEquals("synchToTime", 1, selection.size()); + assertTrue("synchToTime", selection.get(0) instanceof TmfSyncMessage); + msg = (TmfSyncMessage) selection.get(0); + assertEquals("synchToTime", TC_002_MESSAGE_NAME, msg.getName()); + assertEquals(0, TC_002_TIME_VALUE.compareTo(msg.getStartTime(), false)); + assertEquals("synchToTime", TC_002_START_OCCURRANCE, msg.getStartOccurrence()); + assertEquals("synchToTime", TC_002_END_OCCURRANCE, msg.getEndOccurrence()); + assertEquals(TC_002_START_LIFELINE, msg.getStartLifeline().getName()); + assertEquals(TC_002_END_LIFELINE, msg.getEndLifeline().getName()); + + + /* + * Test Case: 003 + * Description: Verify synchToTime (timestamp doesn't exist in trace), no selection of message in page + * Verified Methods: loader.syncToTime(), loader.moveToMessage(), loader.moveToPage() + * Expected result: Move to correct page, currentTime is updated so that focus on the currentTime, but no selection. + */ + fFacility.getLoader().firstPage(); + + fFacility.getTrace().broadcast(new TmfTimeSynchSignal(this, TC_003_TIME_VALUE)); + fFacility.getLoader().waitForCompletion(); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + assertEquals("synchToTime", 0, TC_003_TIME_VALUE.compareTo(fFacility.getLoader().getCurrentTime(), false)); + assertEquals("synchToTime", TC_003_PAGE_VALUE, fFacility.getLoader().currentPage()); + selection = fFacility.getSdView().getSDWidget().getSelection(); + assertNotNull("synchToTime", selection); + assertEquals("synchToTime", 0, selection.size()); + + /* + * Test Case: 004 + * Description: Verify synchToTime (timestamp < experiment time range start) + * Verified Methods: loader.syncToTime(), loader.moveToMessage(), loader.moveToPage() + * Expected result: Move to first page, focus on the beginning of the page, but no selection. + */ + fFacility.getTrace().broadcast(new TmfTimeSynchSignal(this, TmfTimestamp.BigBang)); + fFacility.getLoader().waitForCompletion(); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + assertEquals("synchToTime", TC_004_PAGE_VALUE, fFacility.getLoader().currentPage()); + selection = fFacility.getSdView().getSDWidget().getSelection(); + assertNotNull("synchToTime", selection); + assertEquals("synchToTime", 0, selection.size()); + + /* + * Test Case: 005 + * Description: Verify synchToTime (timestamp > experiment time range end) + * Verified Methods: loader.syncToTime(), loader.moveToMessage(), loader.moveToPage() + * Expected result: Move to last page, focus on the end of the page, but no selection. + */ + fFacility.getTrace().broadcast(new TmfTimeSynchSignal(this, TmfTimestamp.BigCrunch)); + fFacility.getLoader().waitForCompletion(); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + assertEquals("synchToTime", TC_005_PAGE_VALUE, fFacility.getLoader().currentPage()); + selection = fFacility.getSdView().getSDWidget().getSelection(); + assertNotNull(selection); + assertEquals("synchToTime", 0, selection.size()); + + /* + * Test Case: 006 + * Description: Verify synchToTime (timestamp of last message in page) + * Verified Methods: loader.syncToTime(), loader.moveToMessage(), loader.moveToPage(), loader.moveToMessageInPage() + * Expected result: Move to correct page, selection of last message in page. + */ + fFacility.getTrace().broadcast(new TmfTimeSynchSignal(this, TC_006_TIME_VALUE)); + fFacility.getLoader().waitForCompletion(); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + assertEquals("synchToTime", TC_006_PAGE_VALUE, fFacility.getLoader().currentPage()); + selection = fFacility.getSdView().getSDWidget().getSelection(); + assertNotNull("synchToTime", selection); + assertEquals("synchToTime", 1, selection.size()); + msg = (TmfSyncMessage) selection.get(0); + assertEquals("synchToTime", TC_006_START_OCCURRANCE, msg.getStartOccurrence()); + assertEquals("synchToTime", TC_006_END_OCCURRANCE, msg.getEndOccurrence()); + + /* + * Test Case: 007 + * Description: Verify synchToTime (timestamp of first message in page) + * Verified Methods: loader.syncToTime(), loader.moveToMessage(), loader.moveToPage() + * Expected result: Move to correct page, selection of last message in page. + */ + fFacility.getTrace().broadcast(new TmfTimeSynchSignal(this, TC_007_TIME_VALUE)); + fFacility.getLoader().waitForCompletion(); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + assertEquals("synchToTime", TC_007_PAGE_VALUE, fFacility.getLoader().currentPage()); + selection = fFacility.getSdView().getSDWidget().getSelection(); + assertNotNull("synchToTime", selection); + msg = (TmfSyncMessage) selection.get(0); + assertEquals("synchToTime", 1, selection.size()); + assertEquals("synchToTime", TC_007_START_OCCURRANCE, msg.getStartOccurrence()); + assertEquals("synchToTime", TC_007_END_OCCURRANCE, msg.getEndOccurrence()); + + /* + * Test Case: 008 + * Description: Verify time range signal (start, end time and current time are in same page) + * Verified Methods: loader.synchToTimeRange(), loader.moveToMessage(), loader.moveToMessageInPage() + * Expected result: Move to correct page, set focus on current time, but no selection of message. + */ + // 9788.642228395 (page 0) -> 9789.164833324 (page 0) with selected time 9788.642228395 (page 0) + fFacility.getLoader().firstPage(); + TmfTimeRange range = new TmfTimeRange(TC_008_START_TIME_VALUE, TC_008_END_TIME_VALUE); + fFacility.getLoader().waitForCompletion(); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + fFacility.getTrace().broadcast(new TmfRangeSynchSignal(this, range, TC_008_TIME_VALUE)); + assertEquals("synchToTimeRange", TC_008_PAGE_VALUE, fFacility.getLoader().currentPage()); + assertNotNull("synchToTimeRange", fFacility.getLoader().getCurrentTime()); + assertEquals("synchToTimeRange", 0, TC_008_TIME_VALUE.compareTo(fFacility.getLoader().getCurrentTime(), false)); + selection = fFacility.getSdView().getSDWidget().getSelection(); + // We actually don't want something to be selected!!! + assertNotNull("synchToTimeRange", selection); + assertEquals("synchToTimeRange", 0, selection.size()); + + /* + * Test Case: 009 + * Description: Verify time range signal (start and end time are across 2 pages, current time is on 2nd page) + * Verified Methods: loader.synchToTimeRange(), loader.moveToMessage(), loader.moveToPage() + * Expected result: Move to correct page (page of current time), set focus on current time, but no selection of message. + */ + range = new TmfTimeRange(TC_009_START_TIME_VALUE, TC_009_END_TIME_VALUE); + fFacility.getTrace().broadcast(new TmfRangeSynchSignal(this, range, TC_009_TIME_VALUE)); + fFacility.getLoader().waitForCompletion(); + fFacility.delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + assertEquals("synchToTimeRange", TC_009_PAGE_VALUE, fFacility.getLoader().currentPage()); + assertNotNull("synchToTimeRange", fFacility.getLoader().getCurrentTime()); + assertEquals("synchToTimeRange", 0, TC_009_TIME_VALUE.compareTo(fFacility.getLoader().getCurrentTime(), false)); + selection = fFacility.getSdView().getSDWidget().getSelection(); + // We actually don't want something to be selected!!! + assertNotNull("synchToTimeRange", selection); + assertEquals("synchToTimeRange", 0, selection.size()); + + } +} \ No newline at end of file diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/Uml2SDSignalValidator.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/Uml2SDSignalValidator.java new file mode 100644 index 0000000000..8a6870d16c --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/Uml2SDSignalValidator.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl; + +import org.eclipse.linuxtools.tmf.component.TmfComponent; +import org.eclipse.linuxtools.tmf.event.TmfTimeRange; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.signal.TmfEndSynchSignal; +import org.eclipse.linuxtools.tmf.signal.TmfRangeSynchSignal; +import org.eclipse.linuxtools.tmf.signal.TmfSignalHandler; +import org.eclipse.linuxtools.tmf.signal.TmfStartSynchSignal; +import org.eclipse.linuxtools.tmf.signal.TmfTimeSynchSignal; + +/** + * Class to implement that certain signals are sent as well as are sent with correct content. + */ +public class Uml2SDSignalValidator extends TmfComponent implements IUml2SdSignalValidator { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + private int fSignalDepth = 0; + private boolean fIsSignalReceived = false; + private boolean fIsSignalError = false; + private boolean fIsSourceError = false; + private boolean fIsCurrentTimeError = false; + private boolean fIsRangeError = false; + + private Object fSource = null; + private TmfTimestamp fCurrentTimestamp = null; + private TmfTimeRange fCurrentTimeRange = null; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + public Uml2SDSignalValidator() { + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + @TmfSignalHandler + public void synchToTime(TmfTimeSynchSignal signal) { + // Set results so that it can be validated in the test case + setSignalReceived(true); + setSourceError(getSource() != signal.getSource()); + setCurrentTimeError(!getCurrentTime().equals(signal.getCurrentTime())); + } + + @TmfSignalHandler + public void synchToTimeRange(TmfRangeSynchSignal signal) { + // Set results so that it can be validated in the test case + setSignalReceived(true); + setSourceError(getSource() != signal.getSource()); + setCurrentTimeError(!getCurrentTime().equals(signal.getCurrentTime())); + setRangeError(!getCurrentRange().equals(signal.getCurrentRange())); + } + + @TmfSignalHandler + public void startSynch(TmfStartSynchSignal signal) { + fSignalDepth++; + // make sure that the signal which is send by the loader class is not handled by the loader class + // after receiving it. i.e. it must not trigger a another signal + + // Set results so that it can be validated in the test case + setSignalError(fSignalDepth > 1); + } + + @TmfSignalHandler + public void endSynch(TmfEndSynchSignal signal) { + fSignalDepth = fSignalDepth > 0 ? fSignalDepth - 1 : 0; + } + + @Override + public boolean isSignalReceived() { + return fIsSignalReceived; + } + + @Override + public void setSignalReceived(boolean received) { + fIsSignalReceived = received; + } + + @Override + public boolean isSourceError() { + return fIsSourceError; + } + + @Override + public void setSourceError(boolean fIsSourceError) { + this.fIsSourceError = fIsSourceError; + } + + @Override + public boolean isCurrentTimeError() { + return fIsCurrentTimeError; + } + + @Override + public void setCurrentTimeError(boolean fIsCurrentTimeError) { + this.fIsCurrentTimeError = fIsCurrentTimeError; + } + + @Override + public boolean isRangeError() { + return fIsRangeError; + } + + @Override + public void setRangeError(boolean fIsRangeError) { + this.fIsRangeError = fIsRangeError; + } + + @Override + public boolean isSignalError() { + return fIsSignalError; + } + + @Override + public void setSignalError(boolean fIsSignalError) { + this.fIsSignalError = fIsSignalError; + } + + @Override + public Object getSource() { + return fSource; + } + + @Override + public void setSource(Object source) { + fSource = source; + } + + @Override + public TmfTimestamp getCurrentTime() { + return fCurrentTimestamp; + } + + @Override + public void setCurrentTime(TmfTimestamp currentTime) { + fCurrentTimestamp = currentTime == null ? null : new TmfTimestamp(currentTime); + } + + @Override + public TmfTimeRange getCurrentRange() { + return fCurrentTimeRange; + } + + @Override + public void setCurrentRange(TmfTimeRange currentRange) { + fCurrentTimeRange = currentRange == null ? null : new TmfTimeRange(currentRange); + } +}; + diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/Uml2SDTestFacility.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/Uml2SDTestFacility.java new file mode 100644 index 0000000000..aaefb2eda9 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/Uml2SDTestFacility.java @@ -0,0 +1,306 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.linuxtools.tmf.event.TmfEvent; +import org.eclipse.linuxtools.tmf.experiment.TmfExperiment; +import org.eclipse.linuxtools.tmf.parser.ITmfEventParser; +import org.eclipse.linuxtools.tmf.signal.TmfExperimentSelectedSignal; +import org.eclipse.linuxtools.tmf.trace.ITmfTrace; +import org.eclipse.linuxtools.tmf.trace.TmfTraceStub; +import org.eclipse.linuxtools.tmf.ui.tests.TmfUITestPlugin; +import org.eclipse.linuxtools.tmf.ui.tests.uml2sd.trace.TmfUml2SDTestTrace; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.Criteria; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.FilterCriteria; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.FilterListDialog; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl.TmfUml2SDSyncLoader; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.LoadersManager; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; + +/** + * Singleton class to facilitate the test cases. Creates UML2SD view and loader objects as well as provides + * utility methods for interacting with the loader/view. + */ +public class Uml2SDTestFacility { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + private static Uml2SDTestFacility fInstance = null; + + private TmfUml2SDSyncLoader fLoader; + private SDView fSdView; + private TmfTraceStub fTrace = null; + private TmfUml2SDTestTrace fParser = null; + private TmfExperiment fExperiment = null; + + private boolean fIsInitialized = false; + private boolean fIsGlobalTest = false; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + private Uml2SDTestFacility() { + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + public static Uml2SDTestFacility getInstance() { + if (fInstance == null) { + fInstance = new Uml2SDTestFacility(); + } + return fInstance; + } + + /** + * Initial the test facility. + * @param isGlobal true if multiple test cases are run in sequence (AllTests) or false for a single test case + */ + public void init(boolean isGlobal) { + + if (!fIsInitialized) { + fIsGlobalTest = isGlobal; + + fParser = new TmfUml2SDTestTrace(); + fTrace = setupTrace(fParser); + + IViewPart view; + try { + view = PlatformUI.getWorkbench() + .getActiveWorkbenchWindow() + .getActivePage() + .showView("org.eclipse.linuxtools.tmf.ui.tmfUml2SDSyncView"); //$NON-NLS-1$ + + } catch (PartInitException e) { + throw new RuntimeException(e); + } + + fSdView = (SDView) view; + fLoader = (TmfUml2SDSyncLoader)LoadersManager.getInstance().createLoader( + "org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl.TmfUml2SDSyncLoader", fSdView); //$NON-NLS-1$ + + delay(3000); + fIsInitialized = true; + } + } + + + private TmfTraceStub setupTrace(ITmfEventParser parser) { + + try { + // Create test trace object + URL location = FileLocator.find(TmfUITestPlugin.getDefault().getBundle(), new Path("tracesets/sdEvents"), null); //$NON-NLS-1$ + File test = new File(FileLocator.toFileURL(location).toURI()); + return new TmfTraceStub(test.getPath(), 500, true, parser); + } catch (URISyntaxException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + /** + * Dispose the resource + */ + public void dispose() { + if (fIsInitialized && !fIsGlobalTest) { + fExperiment.dispose(); + + // Wait for all Eclipse jobs to finish + waitForJobs(); + + PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().hideView(fSdView); + fIsInitialized = false; + } + } + + /** + * Sleeps current thread or GUI thread for a given time. + * @param waitTimeMillis + */ + public void delay(long waitTimeMillis) { + Display display = Display.getCurrent(); + if (display != null) { + long endTimeMillis = System.currentTimeMillis() + waitTimeMillis; + while(System.currentTimeMillis() < endTimeMillis) { + if (!display.readAndDispatch()) { + display.sleep(); + } + display.update(); + } + } else { + try { + Thread.sleep(waitTimeMillis); + } catch (InterruptedException e) { + // Ignored + } + } + } + + /** + * Waits for all Eclipse jobs to finish + */ + public void waitForJobs() { + while (!Job.getJobManager().isIdle()) { + delay(IUml2SDTestConstants.WAIT_FOR_JOBS_DELAY); + } + } + + /** + * @return current UML2SD loader + */ + public TmfUml2SDSyncLoader getLoader() { + return fLoader; + } + + /** + * @return current SD view + */ + public SDView getSdView() { + return fSdView; + } + + /** + * @return current trace + */ + public TmfTraceStub getTrace() { + return fTrace; + } + + /** + * @return Trace parser + */ + public TmfUml2SDTestTrace getParser() { + return fParser; + } + + /** + * @return current experiment. + */ + public TmfExperiment getExperiment() { + return fExperiment; + } + + /** + * Go to next page; + */ + public void nextPage() { + fLoader.nextPage(); + fLoader.waitForCompletion(); + delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + } + + /** + * Go to previous page. + */ + public void prevPage() { + fLoader.prevPage(); + fLoader.waitForCompletion(); + delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + } + + /** + * Go to last page. + */ + public void lastPage() { + fLoader.lastPage(); + fLoader.waitForCompletion(); + delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + } + + /** + * Go to first page. + */ + public void firstPage() { + fLoader.firstPage(); + fLoader.waitForCompletion(); + delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + } + + /** + * @param page number to set + */ + public void setPage(int page) { + fLoader.pageNumberChanged(page);; + fLoader.waitForCompletion(); + delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + } + + /** + * @see org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl.selectExperiment(boolean) + */ + public void selectExperiment() { + this.selectExperiment(true); + } + + /** + * Selects the experiment. + * @param wait true to wait for indexing to finish else false + */ + public void selectExperiment(boolean wait) { + fTrace = setupTrace(fParser); + + ITmfTrace traces[] = new ITmfTrace[1]; + traces[0] = fTrace; + fExperiment = new TmfExperiment(TmfEvent.class, "TestExperiment", traces); //$NON-NLS-1$ + fTrace.broadcast(new TmfExperimentSelectedSignal(this, fExperiment)); + if (wait) { + waitForJobs(); + delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + } + } + + /** + * Disposes the experiment. + */ + public void disposeExperiment() { + fExperiment.dispose(); + delay(IUml2SDTestConstants.GUI_REFESH_DELAY); + } + + /** + * Creates some global filter criteria and saves them to disk. + */ + public void createFilterCriteria() { + // Create Filter Criteria and save tme + List filterToSave = new ArrayList(); + Criteria criteria = new Criteria(); + criteria.setLifeLineSelected(true); + criteria.setExpression(IUml2SDTestConstants.FIRST_PLAYER_NAME); + filterToSave.add(new FilterCriteria(criteria, true, false)); + + criteria = new Criteria(); + criteria.setSyncMessageSelected(true); + criteria.setExpression("BALL_.*"); //$NON-NLS-1$ + filterToSave.add(new FilterCriteria(criteria, true, false)); + FilterListDialog.saveFiltersCriteria(filterToSave); + } + + +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/Uml2SDTestTimestamp.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/Uml2SDTestTimestamp.java new file mode 100644 index 0000000000..0fda30caca --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/impl/Uml2SDTestTimestamp.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.impl; + +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; + +public class Uml2SDTestTimestamp extends TmfTimestamp { + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + public Uml2SDTestTimestamp(long value) { + super(value, IUml2SDTestConstants.TIME_SCALE); + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/load/AllTests.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/load/AllTests.java new file mode 100644 index 0000000000..0c4ff39821 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/load/AllTests.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.load; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + + public static Test suite() { + + TestSuite suite = new TestSuite(AllTests.class.getName()); + //$JUnit-BEGIN$ + suite.addTestSuite(LoadersManagerTest.class); + //$JUnit-END$ + return suite; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/load/LoadersManagerTest.java b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/load/LoadersManagerTest.java new file mode 100644 index 0000000000..8349b5a46f --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/src/org/eclipse/linuxtools/tmf/ui/tests/views/uml2sd/load/LoadersManagerTest.java @@ -0,0 +1,176 @@ +/********************************************************************** + * Copyright (c) 2011 Ericsson. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.views.uml2sd.load; + +import junit.framework.TestCase; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.IUml2SDLoader; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.LoadersManager; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.PlatformUI; + +public class LoadersManagerTest extends TestCase { + + private final static String SDVIEW_WITH_ONE_LOADER = "org.eclipse.linuxtools.tmf.ui.tests.testSDView1Loader"; //$NON-NLS-1$ + private final static String SDVIEW_WITH_MULTIPLE_LOADER = "org.eclipse.linuxtools.tmf.ui.tests.testSDView2Loaders"; //$NON-NLS-1$ + private final static String TEST_LOADER_CLASS_NAME = "org.eclipse.linuxtools.tmf.ui.tests.uml2sd.load.TestLoaders"; //$NON-NLS-1$ + private final static String TMF_UML2SD_LOADER_CLASS_NAME = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl.TmfUml2SDSyncLoader"; //$NON-NLS-1$ + + private static final String LOADER_TAG = "uml2SDLoader"; //$NON-NLS-1$ + private static final String LOADER_PREFIX = LOADER_TAG + "."; //$NON-NLS-1$ + + @Override + public void setUp() throws Exception { + super.setUp(); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + + @SuppressWarnings("nls") + public void testLoaderManager() { + + SDView view = null; + try { + + /* + * Test creation of a loader (one per SD view) + */ + + // Open view + // Note this will create the default loader! + view = (SDView)PlatformUI.getWorkbench() + .getActiveWorkbenchWindow() + .getActivePage() + .showView(SDVIEW_WITH_ONE_LOADER); + + IUml2SDLoader defaultLoader = LoadersManager.getInstance().getCurrentLoader(SDVIEW_WITH_ONE_LOADER, view); + assertNotNull("testLoaderManager", defaultLoader); + + // Test createLoader where loader doesn't exist + assertNull("testLoaderManager", LoadersManager.getInstance().createLoader("blabla", view)); + + // Test createLoader + IUml2SDLoader loader = LoadersManager.getInstance().createLoader(TEST_LOADER_CLASS_NAME, view); + + assertNotNull("testLoaderManager", loader); + assertEquals("testLoaderManager", "Test Loader", loader.getTitleString()); + assertEquals("testLoaderManager", loader, LoadersManager.getInstance().getCurrentLoader(SDVIEW_WITH_ONE_LOADER)); + + // compare loader and default loader. Even if they are the same class, they are different instances + assertFalse("testLoaderManager", loader==defaultLoader); + + // test getCurrentLoader(viewId, view) + IUml2SDLoader loader2 = LoadersManager.getInstance().getCurrentLoader(SDVIEW_WITH_ONE_LOADER, view); + assertEquals("testLoaderManager", loader, loader2); + + // Hide the view + PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().hideView(view); + + // test that view restores the previous associated loader + view = (SDView)PlatformUI.getWorkbench() + .getActiveWorkbenchWindow() + .getActivePage() + .showView(SDVIEW_WITH_ONE_LOADER); + + // Well, this is the only way test which loader is set + assertEquals("testLoaderManager", "Sequence Diagram - First Page", view.getFrame().getName()); + + // Test view == null + assertNull("testLoaderManager", LoadersManager.getInstance().createLoader(TEST_LOADER_CLASS_NAME, null)); + + // Hide the view + PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().hideView(view); + + /* + * Test creation of loaders with re-uses the same SD view + */ + + // test that view restores the previous associated loader + view = (SDView)PlatformUI.getWorkbench() + .getActiveWorkbenchWindow() + .getActivePage() + .showView(SDVIEW_WITH_MULTIPLE_LOADER); + + // Test that default loader is set. Note that 2 default loaders are define in the plugin.xml and the + // the first one should be selected. + + // Well, this is the only way test which loader is set + assertEquals("testLoaderManager", "Sequence Diagram - First Page", view.getFrame().getName()); + + loader = LoadersManager.getInstance().getCurrentLoader(SDVIEW_WITH_MULTIPLE_LOADER, view); + assertNotNull("testLoaderManager", loader); + assertEquals("testLoaderManager", "Test Loader", loader.getTitleString()); + assertEquals("testLoaderManager", loader, LoadersManager.getInstance().getCurrentLoader(SDVIEW_WITH_MULTIPLE_LOADER)); + + // Test createLoader for loader with class name TMF_UML2SD_LOADER_CLASS_NAME + loader = LoadersManager.getInstance().createLoader(TMF_UML2SD_LOADER_CLASS_NAME, view); + + assertNotNull("testLoaderManager", loader); + assertEquals("testLoaderManager", "Component Interactions", loader.getTitleString()); + assertEquals("testLoaderManager", loader, LoadersManager.getInstance().getCurrentLoader(SDVIEW_WITH_MULTIPLE_LOADER)); + + // Verify if the correct loader is stored in the preferences as the current loader for this view + assertEquals("testLoaderManager", TMF_UML2SD_LOADER_CLASS_NAME, getSavedLoader(SDVIEW_WITH_MULTIPLE_LOADER)); + + // Test createLoader for loader with class name TEST_LOADER_CLASS_NAME + loader = LoadersManager.getInstance().createLoader(TEST_LOADER_CLASS_NAME, view); + + assertNotNull("testLoaderManager", loader); + assertEquals("testLoaderManager", "Test Loader", loader.getTitleString()); + assertEquals("testLoaderManager", loader, LoadersManager.getInstance().getCurrentLoader(SDVIEW_WITH_MULTIPLE_LOADER)); + + // Verify if the correct loader is stored in the preferences as the current loader for this view + assertEquals("testLoaderManager", TEST_LOADER_CLASS_NAME, getSavedLoader(SDVIEW_WITH_MULTIPLE_LOADER)); + assertEquals("testLoaderManager", TEST_LOADER_CLASS_NAME, LoadersManager.getInstance().getSavedLoader(SDVIEW_WITH_MULTIPLE_LOADER)); + + // Hide the view + PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().hideView(view); + + // Test reset loader + LoadersManager.getInstance().resetLoader(SDVIEW_WITH_MULTIPLE_LOADER); + + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @SuppressWarnings("unused") + private void delay(long waitTimeMillis) { + Display display = Display.getCurrent(); + if (display != null) { + long endTimeMillis = System.currentTimeMillis() + waitTimeMillis; + while(System.currentTimeMillis() < endTimeMillis) { + if (!display.readAndDispatch()) { + display.sleep(); + } + display.update(); + } + } else { + try { + Thread.sleep(waitTimeMillis); + } catch (InterruptedException e) { + // Ignored + } + } + } + + private String getSavedLoader(String viewId) { + IPreferenceStore p = TmfUiPlugin.getDefault().getPreferenceStore(); + return p.getString(LOADER_PREFIX + viewId); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui.tests/tracesets/sdEvents b/org.eclipse.linuxtools.tmf.ui.tests/tracesets/sdEvents new file mode 100644 index 0000000000000000000000000000000000000000..85e44a6ece791a628864d4ee3f740bd09f2fe2d1 GIT binary patch literal 5722500 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 z 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

<)bQYZVM-r3HqDopnh+5<&)|HIT;%(wPOc!g3 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 z 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

=v~S2)k= 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�} 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

#wBK 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

D0FnPcTNueYDmu%YBu? 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 R5Muu)g697(=KoIce*sBEdD8#@ literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui.tests/widgetStubs/org/eclipse/linuxtools/tmf/ui/tests/uml2sd/load/TestLoaders.java b/org.eclipse.linuxtools.tmf.ui.tests/widgetStubs/org/eclipse/linuxtools/tmf/ui/tests/uml2sd/load/TestLoaders.java new file mode 100644 index 0000000000..40881c8c55 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/widgetStubs/org/eclipse/linuxtools/tmf/ui/tests/uml2sd/load/TestLoaders.java @@ -0,0 +1,476 @@ +package org.eclipse.linuxtools.tmf.ui.tests.uml2sd.load; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.AsyncMessageReturn; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.BasicExecutionOccurrence; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.EllipsisisMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.ExecutionOccurrence; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Frame; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.HotSpot; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.LifelineCategories; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Stop; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SyncMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SyncMessageReturn; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IImage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDFilterProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDFindProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDGraphNodeSupporter; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDPagingProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.Criteria; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.FilterCriteria; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.IUml2SDLoader; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.ISelectionListener; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.PlatformUI; + +// Referenced classes of package org.eclipse.hyades.uml2sd.ztest: +// OpenToolBox + +public class TestLoaders implements IUml2SDLoader, ISDFindProvider, ISDFilterProvider, ISDPagingProvider, ISelectionListener { + + public SDView v; + public int page; + protected static URL BASIC_URL = null; + + private List findResults = new ArrayList(); + private Criteria findCriteria; + private int currentFindIndex = 0; + + private Frame savedFrame = null; + + static { + try { + BASIC_URL = new URL("platform", "localhost", "plugin"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + } catch (Exception E) { + System.err.println(E); + } + } + + public TestLoaders() { + this(""); //$NON-NLS-1$ + } + + public TestLoaders(String name) { + page = 1; + } + + @Override + public void setViewer(SDView j) { + PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService().addPostSelectionListener(this); + v = j; + v.setSDPagingProvider(this); + v.setSDFindProvider(this); + v.setSDFilterProvider(this); + page = 1; + createFrame(); + } + + @Override + public boolean hasNextPage() { + return page == 1; + } + + @Override + public boolean hasPrevPage() { + return page == 2; + } + + @Override + public void prevPage() { + page--; + createFrame(); + } + + @Override + public void nextPage() { + page++; + createFrame(); + } + + @SuppressWarnings("nls") + private void createFrame() { + Frame testFrame = new Frame(); + if (page == 1) { + testFrame.setName("Sequence Diagram - First Page"); + LifelineCategories tt[] = new LifelineCategories[2]; + tt[0] = new LifelineCategories(); + tt[1] = new LifelineCategories(); + tt[1].setName("Categorie 1"); + tt[1].setImage(new LocalImageImpl("obj16/node_obj.gif")); + tt[0].setImage(new LocalImageImpl("obj16/class.gif")); + tt[0].setName("Categorie 0"); + testFrame.setLifelineCategories(tt); + Lifeline lifeline = new Lifeline(); + lifeline.setName("LifeLine 0"); + testFrame.addLifeLine(lifeline); + EllipsisisMessage mn = new EllipsisisMessage(); + lifeline.getNewEventOccurrence(); + mn.setStartLifeline(lifeline); + mn.setName("******************* EllipsisisMessage TEST ****************"); + testFrame.addMessage(mn); + SyncMessage mn3 = new SyncMessage(); + mn3.setStartLifeline(lifeline); + testFrame.addMessage(mn3); + SyncMessage mn2 = new SyncMessage(); + lifeline.getNewEventOccurrence(); + lifeline.setCategory(0); + mn2.setEndLifeline(lifeline); + mn2.setName("*******************Sync TEST ****************"); + testFrame.addMessage(mn2); + for (int i = 1; i < 300; i++) { + lifeline = new Lifeline(); + lifeline.setName((new StringBuilder("LifeLine ")).append(i).toString()); + lifeline.setCategory(1); + testFrame.addLifeLine(lifeline); + SyncMessage m3 = new SyncMessage(); + testFrame.getLifeline(i - 1).getNewEventOccurrence(); + m3.setStartLifeline(testFrame.getLifeline(i - 1)); + m3.setEndLifeline(testFrame.getLifeline(i)); + m3.setName((new StringBuilder("Sync Message ")).append(i).toString()); + testFrame.addMessage(m3); +// if (i == 11) +// m3.setTime(new TmfTimestamp(i - 400)); +// else if (i == 6) +// m3.setTime(new TmfTimestamp(i)); +// else + m3.setTime(new TmfTimestamp(i + 1)); + } + + for (int i = testFrame.lifeLinesCount() - 1; i > 0; i--) { + SyncMessageReturn m = new SyncMessageReturn(); + testFrame.getLifeline(i).getNewEventOccurrence(); + m.setStartLifeline(testFrame.getLifeline(i)); + m.setEndLifeline(testFrame.getLifeline(i - 1)); + testFrame.addMessage(m); + m.setName((new StringBuilder("Sync Message return ")).append(i).toString()); + if (i + 1 < testFrame.lifeLinesCount()) { + SyncMessage h = testFrame.getSyncMessage(i + 1); + m.setMessage(h); + } + } + + for (int i = 0; i < testFrame.lifeLinesCount(); i++) + if (i > 0) { + ExecutionOccurrence occ = new ExecutionOccurrence(); + occ.setStartOccurrence(testFrame.getSyncMessage(i).getEventOccurrence() + 1); + occ.setEndOccurrence(testFrame.getSyncMessageReturn(testFrame.syncMessageReturnCount() - i).getEventOccurrence()); + testFrame.getLifeline(i).addExecution(occ); + occ.setName("******************* Execution Occurance TEST ****************"); + } + + Stop s = new Stop(); + s.setLifeline(testFrame.getLifeline(1)); + s.setEventOccurrence(testFrame.getLifeline(1).getNewEventOccurrence()); + testFrame.getLifeline(1).addNode(s); + HotSpot gg = new HotSpot(); + gg.setImage(new LocalImageImpl("obj16/plus_obj.gif")); + gg.setExecution((BasicExecutionOccurrence) testFrame.getLifeline(1).getExecutions().get(0)); + AsyncMessageReturn m = new AsyncMessageReturn(); + m.setStartLifeline(testFrame.getLifeline(1)); + m.setEndLifeline(testFrame.getLifeline(3)); + m.setStartOccurrence(2); + m.setEndOccurrence(6); + m.setStartTime(new TmfTimestamp(2)); + m.setEndTime(new TmfTimestamp(6)); + m.setName("*******************Async TEST ****************"); + testFrame.addMessage(m); + v.setFrame(testFrame); + v.getSDWidget().setReorderMode(true); + } else { + +// if (page == 2) { + testFrame.setName("Sequence Diagram"); + Lifeline lifeline = new Lifeline(); + lifeline.setName("LifeLine 0"); + testFrame.addLifeLine(lifeline); + lifeline = new Lifeline(); + lifeline.setName("LifeLine 1"); + testFrame.addLifeLine(lifeline); + for (int i = 1; i < 30; i++) { + SyncMessage m3 = new SyncMessage(); + m3.autoSetStartLifeline(testFrame.getLifeline(0)); + m3.autoSetEndLifeline(testFrame.getLifeline(0)); + m3.setName((new StringBuilder("Message ")).append(i).toString()); + testFrame.addMessage(m3); + SyncMessageReturn m = new SyncMessageReturn(); + m.autoSetStartLifeline(testFrame.getLifeline(0)); + m.autoSetEndLifeline(testFrame.getLifeline(0)); + testFrame.addMessage(m); + m.setName((new StringBuilder("Message return ")).append(i).toString()); + ExecutionOccurrence occ = new ExecutionOccurrence(); + occ.setStartOccurrence(testFrame.getSyncMessage(i - 1).getEventOccurrence()); + occ.setEndOccurrence(testFrame.getSyncMessageReturn(i - 1).getEventOccurrence()); + testFrame.getLifeline(0).addExecution(occ); + } + } + v.setFrame(testFrame); + } + + /* + * (non-Javadoc) + * @see org.eclipse.hyades.uml2sd.ui.actions.provider.ISDFindProvider#find(org.eclipse.hyades.uml2sd.ui.actions.widgets.Criteria) + */ + @Override + public boolean find(Criteria toSearch) { + Frame frame = v.getFrame(); + + if (frame == null) + return false; + if (findResults == null || findCriteria == null || !findCriteria.compareTo(toSearch)) { + findResults = new ArrayList(); + findCriteria = toSearch; + if (findCriteria.isLifeLineSelected()) { + for (int i = 0; i < frame.lifeLinesCount(); i++) { + if (findCriteria.matches(frame.getLifeline(i).getName())) + findResults.add(frame.getLifeline(i)); + } + + } + ArrayList msgs = new ArrayList(); + if (findCriteria.isSyncMessageSelected()) { + for (int i = 0; i < frame.syncMessageCount(); i++) { + if (findCriteria.matches(frame.getSyncMessage(i).getName())) + msgs.add(frame.getSyncMessage(i)); + } + + for (int i = 0; i < frame.syncMessageReturnCount(); i++) + if (findCriteria.matches(frame.getSyncMessageReturn(i).getName())) + msgs.add(frame.getSyncMessageReturn(i)); + + } + // if(msgs.size() > 0) { + // GraphNode temp[] = msgs.toArray(new GraphNode[0]); + // Arrays.sort(temp, new DateComparator()); + // findResults.addAll(Arrays.asList(temp)); + // } + + msgs = new ArrayList(); + if (findCriteria.isAsyncMessageSelected()) { + for (int i = 0; i < frame.asyncMessageCount(); i++) + if (findCriteria.matches(frame.getAsyncMessage(i).getName())) + msgs.add(frame.getAsyncMessage(i)); + + for (int i = 0; i < frame.asyncMessageReturnCount(); i++) + if (findCriteria.matches(frame.getAsyncMessageReturn(i).getName())) + msgs.add(frame.getAsyncMessageReturn(i)); + + } + // if(msgs.size() > 0) { + // GraphNode temp[] = msgs.toArray(new GraphNode[0]); + // Arrays.sort(temp, new DateComparator()); + // findResults.addAll(Arrays.asList(temp)); + // } + + @SuppressWarnings("rawtypes") + List selection = v.getSDWidget().getSelection(); + if (selection != null && selection.size() == 1) + currentFindIndex = findResults.indexOf(selection.get(0)) + 1; + else + currentFindIndex = 0; + } else { + currentFindIndex++; + } + if (findResults.size() > currentFindIndex) { + GraphNode current = (GraphNode) findResults.get(currentFindIndex); + v.getSDWidget().moveTo(current); + return true; + } else { + // return notFoundYet(findCriteria); // search in other page + return false; + } + // return false; + } + + @Override + public void cancel() { + findResults = null; + findCriteria = null; + currentFindIndex = 0; + } + + public boolean isLifelineSupported() { + return false; + } + + public boolean isSyncMessageSupported() { + return false; + } + + public boolean isSyncMessageReturnSupported() { + return false; + } + + public boolean isAsyncMessageSupported() { + return false; + } + + public boolean isAsyncMessageReturnSupported() { + return false; + } + + public boolean isStopSupported() { + return false; + } + + public Action getFindAction() { + return null; + } + + @Override + public boolean filter(List filters) { + + if (savedFrame != null) { + savedFrame = v.getFrame(); + } + + Frame frame = v.getFrame(); + + if (frame == null) { + return false; + } + + if (filters.size() != 1) { + return false; + } + + FilterCriteria filterCriteria = (FilterCriteria) filters.get(0); + + // One way is to set visiblity of the item, but this only works for messages and not + // for lifelines! It's better to create a new frame without the filtered messages. + boolean found = false; + if (filterCriteria.getCriteria().isSyncMessageSelected()) { + for (int i = 0; i < frame.syncMessageCount(); i++) { + if (filterCriteria.getCriteria().matches(frame.getSyncMessage(i).getName())) { + frame.getSyncMessage(i).setVisible(false); + found = true; + } + } + + for (int i = 0; i < frame.syncMessageReturnCount(); i++) + if (filterCriteria.getCriteria().matches(frame.getSyncMessageReturn(i).getName())) { + frame.getSyncMessageReturn(i).setVisible(false); + found = true; + } + } + + v.getSDWidget().redraw(); + return found; + } + + public ArrayList getCurrentFilters() { + return null; + } + + @SuppressWarnings("nls") + @Override + public String getTitleString() { + return "Test Loader"; + } + + @Override + public void dispose() { + } + + + @Override + public void selectionChanged(IWorkbenchPart part, ISelection selection) { + ISelection sel = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService().getSelection(); + if (sel != null && (sel instanceof StructuredSelection)) { + StructuredSelection stSel = (StructuredSelection) sel; + if (stSel.getFirstElement() instanceof HotSpot) { + // OpenToolBox gg = new OpenToolBox(v); + // gg.run(); + } + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.hyades.uml2sd.ui.actions.provider.ISDGraphNodeSupporter#isNodeSupported(int) + */ + @Override + public boolean isNodeSupported(int nodeType) { + switch (nodeType) { + case ISDGraphNodeSupporter.LIFELINE: + case ISDGraphNodeSupporter.SYNCMESSAGE: + case ISDGraphNodeSupporter.SYNCMESSAGERETURN: + case ISDGraphNodeSupporter.ASYNCMESSAGE: + case ISDGraphNodeSupporter.ASYNCMESSAGERETURN: + case ISDGraphNodeSupporter.STOP: + return true; + + default: + break; + } + return false; + } + + @Override + public String getNodeName(int nodeType, String loaderClassName) { + return null; + } + + public class LocalImageImpl implements IImage { + protected Image img; + + public LocalImageImpl(String file) { + img = null; + img = getResourceImage(file); + } + + public LocalImageImpl(Image img_) { + img = null; + img = img_; + } + + @SuppressWarnings("nls") + public Image getResourceImage(String _name) { + ImageDescriptor img; + try { + URL url = new URL(BASIC_URL, (new StringBuilder("plugin/org.eclipse.linuxtools.tmf.ui/icons/")).append(_name).toString()); + img = ImageDescriptor.createFromURL(url); + return img.createImage(); + } catch (Exception e) { + System.err.println(e); + } + return null; + } + + @Override + public Object getImage() { + return img; + } + + @Override + public void dispose() { + if (img != null) + img.dispose(); + } + + } + + @Override + public void firstPage() { + page = 0; + createFrame(); + + } + + @Override + public void lastPage() { + page = 2; + createFrame(); + } +} \ No newline at end of file diff --git a/org.eclipse.linuxtools.tmf.ui.tests/widgetStubs/org/eclipse/linuxtools/tmf/ui/tests/uml2sd/trace/TmfUml2SDTestTrace.java b/org.eclipse.linuxtools.tmf.ui.tests/widgetStubs/org/eclipse/linuxtools/tmf/ui/tests/uml2sd/trace/TmfUml2SDTestTrace.java new file mode 100644 index 0000000000..dfd4b85c80 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui.tests/widgetStubs/org/eclipse/linuxtools/tmf/ui/tests/uml2sd/trace/TmfUml2SDTestTrace.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.tests.uml2sd.trace; + +import java.io.EOFException; +import java.io.IOException; +import java.io.RandomAccessFile; + +import org.eclipse.linuxtools.tmf.event.TmfEvent; +import org.eclipse.linuxtools.tmf.event.TmfEventContent; +import org.eclipse.linuxtools.tmf.event.TmfEventField; +import org.eclipse.linuxtools.tmf.event.TmfEventReference; +import org.eclipse.linuxtools.tmf.event.TmfEventSource; +import org.eclipse.linuxtools.tmf.event.TmfEventType; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.parser.ITmfEventParser; +import org.eclipse.linuxtools.tmf.trace.ITmfTrace; +import org.eclipse.linuxtools.tmf.trace.TmfContext; +import org.eclipse.linuxtools.tmf.trace.TmfLocation; +import org.eclipse.linuxtools.tmf.trace.TmfTraceStub; + +public class TmfUml2SDTestTrace implements ITmfEventParser { + + @Override + @SuppressWarnings({ "unchecked", "nls" }) + public TmfEvent parseNextEvent(ITmfTrace eventStream, TmfContext context) throws IOException { + if (! (eventStream instanceof TmfTraceStub)) { + return null; + } + + // Highly inefficient... + RandomAccessFile stream = ((TmfTraceStub) eventStream).getStream(); + + String name = eventStream.getName(); + name = name.substring(name.lastIndexOf('/') + 1); + + long location = 0; + if (context != null) + location = ((TmfLocation) (context.getLocation())).getLocation(); + stream.seek(location); + + try { + long ts = stream.readLong(); + String source = stream.readUTF(); + String type = stream.readUTF(); + String reference = stream.readUTF(); + String sender = stream.readUTF(); + String receiver = stream.readUTF(); + String signal = stream.readUTF(); + + TmfEventReference tmfReference = new TmfEventReference(reference); + TmfEventSource tmfSource = new TmfEventSource(source); + String[] labels = {"sender", "receiver", "signal"}; + + TmfEventType tmfEventType = new TmfEventType(type, labels); + TmfEvent tmfEvent = new TmfEvent(new TmfTimestamp(ts, (byte)-9), tmfSource, tmfEventType, tmfReference); + + String content = "["; + content += sender; + content += "," + receiver; + content += "," + signal; + content += "]"; + + TmfEventContent tmfContent = new TmfEventContent(tmfEvent, content) { + @Override + public void parseContent() { + String raw = (String) fRawContent; + int i = raw.indexOf(","); + String sender = raw.substring(1, i); + int k = raw.indexOf(",", i+1); + String receiver = raw.substring(i+1, k); + i = raw.indexOf(",", k+1); + String signal = raw.substring(k+1, raw.length() - 1); + fFields = new Object[3]; + fFields[0] = new TmfEventField(this, "sender", sender); + fFields[1] = new TmfEventField(this, "receiver", receiver);; + fFields[2] = new TmfEventField(this, "signal", signal);; + } + }; + tmfEvent.setContent(tmfContent); + + return tmfEvent; + } catch (EOFException e) { + } + return null; + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/.options b/org.eclipse.linuxtools.tmf.ui/.options new file mode 100644 index 0000000000..de185c71ff --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/.options @@ -0,0 +1,7 @@ +org.eclipse.linuxtools.tmf.ui/error=false +org.eclipse.linuxtools.tmf.ui/warning=false +org.eclipse.linuxtools.tmf.ui/info=false + +org.eclipse.linuxtools.tmf.ui/index=false +org.eclipse.linuxtools.tmf.ui/display=false +org.eclipse.linuxtools.tmf.ui/sorting=false diff --git a/org.eclipse.linuxtools.tmf.ui/META-INF/MANIFEST.MF b/org.eclipse.linuxtools.tmf.ui/META-INF/MANIFEST.MF index c6e920bc8d..3da8c94dd1 100644 --- a/org.eclipse.linuxtools.tmf.ui/META-INF/MANIFEST.MF +++ b/org.eclipse.linuxtools.tmf.ui/META-INF/MANIFEST.MF @@ -2,12 +2,12 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.linuxtools.tmf.ui;singleton:=true -Bundle-Version: 0.3.1.qualifier +Bundle-Version: 0.3.2.qualifier Bundle-Activator: org.eclipse.linuxtools.tmf.ui.TmfUiPlugin Bundle-Vendor: %Bundle-Vendor Require-Bundle: org.eclipse.ui, org.eclipse.core.runtime, - org.eclipse.linuxtools.tmf;bundle-version="0.2.0", + org.eclipse.linuxtools.tmf;bundle-version="0.3.2", org.eclipse.core.resources;bundle-version="3.6.0", org.eclipse.ui.ide;bundle-version="3.6.0" Bundle-RequiredExecutionEnvironment: JavaSE-1.6 @@ -27,5 +27,16 @@ Export-Package: org.eclipse.linuxtools.tmf.ui, org.eclipse.linuxtools.tmf.ui.views.filter, org.eclipse.linuxtools.tmf.ui.views.project, org.eclipse.linuxtools.tmf.ui.views.timechart, + org.eclipse.linuxtools.tmf.ui.views.uml2sd, + org.eclipse.linuxtools.tmf.ui.views.uml2sd.core, + org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings, + org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.impl, + org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers, + org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider, + org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets, + org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl, + org.eclipse.linuxtools.tmf.ui.views.uml2sd.load, + org.eclipse.linuxtools.tmf.ui.views.uml2sd.preferences, + org.eclipse.linuxtools.tmf.ui.views.uml2sd.util, org.eclipse.linuxtools.tmf.ui.widgets Bundle-Localization: plugin diff --git a/org.eclipse.linuxtools.tmf.ui/icons/elcl16/backward_nav.gif b/org.eclipse.linuxtools.tmf.ui/icons/elcl16/backward_nav.gif new file mode 100644 index 0000000000000000000000000000000000000000..4fb41501036b940c99c8a839d7459c88eb98385c GIT binary patch literal 327 zcmZ?wbhEHb6krfwxXQrr@9+D+zn}g6e)He2XMaAO`UhluzxDUq&A;Dn{du|K&)c1U zKb`yw6#sVf_k*54PiFppGV{-i6@Ol={QGw2@4L-^fP#-_?rjlUS1PoI4}+ox2M@c;!iyI;EP0M4 zDfSD$=;@GFSru%pQBc literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/elcl16/forward_nav.gif b/org.eclipse.linuxtools.tmf.ui/icons/elcl16/forward_nav.gif new file mode 100644 index 0000000000000000000000000000000000000000..e2f8c3e1feeb0541239db4061812008af2225105 GIT binary patch literal 327 zcmZ?wbhEHb6krfwxXQrr@9+D+zn}g6e)He2XMaAO`UhluzxDUq&A;Dn{du|K&)c1U zKb`yw6#sVf_k*54PiFppGV{-i6@Ol={QGw2@4L-^fP#-_?rjlUS1Po@J+-Dz{8ESgwt+{5*!DHj%yExE6hNr2CLGc8SInS{s zf%p?|dHMv5Uuypo=4xzGc2W`Q5bP2Z;_OjzQJkcxFoj=0Ofg7Flus#0T~$PNfx4R7 K(v?Au4Aua3A9>vX literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/elcl16/gotopage_menu.gif b/org.eclipse.linuxtools.tmf.ui/icons/elcl16/gotopage_menu.gif new file mode 100755 index 0000000000000000000000000000000000000000..d4e00ba797ede6ff2d64bb0a9a32af467ad0439f GIT binary patch literal 1024 zcmeH`+e?#S0Ea)#Y>Mf$c_cEeR;ff+p|nz$M^r+*iP(iFgwilA@_^@Uwj8+xomL_i z!#M;aaL#4RIZcZwQY4+`*=;)6=H~Xj-}ip+w(@`IvftHn^;|sgOV3P;Pbi>cbPQk^ zBwZxoBme?9Ww#^E=cTEcML;+;vn!J`%OrFWq!SRx?jR>;v_qo{>e*$Z8Sb0G(CEB+ zR$DEU4=Luh_Q=g$V#5GTR%kQ>J8;WQzG+Gc=e;-98{8B1j4c zWI14l9l1s}MZi|OSH>5(gk{>J=-5lviRPj!!_zWeF zA#V)+$O~2R!`=y#`?={VZm^mc@`)ER!4Dm+{|n&*r6;9j{==1YNokHfV7N0 zP#P#L3mUi|G+e=e5W3xN?En60fhf|;1H(jE+>!O@6Bun3i}Yc{OZz9ekGy?Z1<_5O z%xtVDV@uGkN%zc7dYF%~J$?N{)Joc=fHr2T|J5*c(`28pYLHf&o6qm?qQy_wEEq(m z6Ki{mzvgk1o_c4s^JIr9zG0X1-X?Vv&Zf~LS)B(ykB?_{4Rb!+AzP$u-++kUjF?*` z*{`patX1C|@yHOJN%+ZPO9Z*m?7K^T5mDzgf~cTr4o9M=Kl6#pZ=%0B_dPYGwPE^f zi)6Yz^1?4p?;JZ-a3UtIj@DfCEj2W}m@`)4aqM~cl^-0|)VQL2HIy6cN5iiF0p$;! A9smFU literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/elcl16/next_menu.gif b/org.eclipse.linuxtools.tmf.ui/icons/elcl16/next_menu.gif new file mode 100755 index 0000000000000000000000000000000000000000..9b5b581b2b332e31327ea2c3fcd04c1fd22c3749 GIT binary patch literal 542 zcmZ?wbhEHb6krfwc*el+_s@$zzn}m4{qpzE2Y-J*`}6J6@2{8t{(SKN_lw`}cK!Qt z@86FHzn;zg@nrU|XY+qQU-0YM!rw2K|9Q3M@5kf+zFhqKoZu9TkEx+%!{<_oh??Km}yIp@C^!#}^>F?ud ze;!T$_k8icm#hALIQjSeiNE)!{eQml|Bn}&Y6SMQi0)|--_;_%w^d?&8UL<2fsGY{ z>njAeR10mY6x!V=yt-IuXT8YAa^bzrqPrSIx7CUqXc61jBDS?gbXTL;(kz}e1$=7? z1eWIuEXxzzQYO5yRCrUF$d*dcEfr$Ra|D*>2(8E!URxyg|Nno6fdh&^Sr{1@!WeWw zP5{LT1ABEtSW|OLYg?GHi>Xl^ht>+ra%E@rLwOa-F1`6p28aVh{+O+ub^6?7rXiM|)^O(2VX=rLl>1#>qhqimE hiHoaCsCrKIR#s3^^qCtLq^K19zR4rx>-$CqYXE?J)(Zdt literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/elcl16/node_end.gif b/org.eclipse.linuxtools.tmf.ui/icons/elcl16/node_end.gif new file mode 100755 index 0000000000000000000000000000000000000000..a1c74077e654beaa820eb84eed4c61ed21ddaad8 GIT binary patch literal 895 zcmb`GF>91@490WNp}O2nf*{z6P$+J3uyj#8+7p*zyR?XS1v7V%uGgcUQ0P$TlErrD zVn@ND;BbS53K{ep_$}H_@t6N+FmSw=OY-D-p1imEYUSC|uqQpamWomPjM`&FHmaCT zr^)Xr*&aOkM)2GJ3l%XD7m3U9-2aTgEqtkF;jlW>WU zD5jDsX_77(&@SMD28&_3v-VV&s;h=-)><+%HCGF@EEW{q)LlK)vq;e!rr{c)k#J;G z(==T(G}#Jkz#t7vXi;M^*g%8DaF(FT2{#L~EPbpcb2kt3EV;0t7;X_3iF!!UYw4C@ zi9Gy(Cezq~en_AY)__48mbAiy#b5&sR+e*BCMUu@%SiOGmMp>}@fEqSpy(N%_=wXO z0594x8nsx7AJAkPJK%(16v7%XNHYm7c(533ptDX?-IU3R&d&$bzcJC^- z6PC^GD%IPJF4voPHQMZ`BQ%AD)JWG&8jk{Q(7X;@3G>jzs`=ZQj!0LY)LUKuiRZI# z<>Bmu!OrQ)xV!yz{9!Qs{<(K&v48V-?sD&ZZso_|_oEk+yTgzDtDV#N!|u|T{;#9= ui~EzUdryC^oxR?hZ2XCf^@W!wGrI@-pT2Fajqh*1dwhPp{pPUKk>!6wk%1!s literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/elcl16/node_start.gif b/org.eclipse.linuxtools.tmf.ui/icons/elcl16/node_start.gif new file mode 100755 index 0000000000000000000000000000000000000000..5d95c27e34f79974222457a9603d406f90db0879 GIT binary patch literal 873 zcmb`Gy=v4^5XDD`bT9YTSfU7z^eB%O3z8Y0=~rB``19BK6q@ANKj zrotL9NW&6ZPc{64iKQ@^$Q6$+1G&Jhr0R zVYFNy-l=uip(PB3LK>vClfgs41DfZ+mM{-ZjM}_exTV*;r4Rilo-OU#_vJh1E7R|f zON*;7{>-L}cefurJX$~a_WaZF*8QW?gGYOx?=61#c|BWudGh$~+2#5E*Ui_*Z!W$Z SZvUO1tiJ#8Yk7Oap8f;d*`SB+G3&QIReYF z1^)m4&p;MX{K>+|z@W>Z1JVuh69ZfPfoTOEI#R9EPhYHHWSHW;aA^Psk4|8qlR|(1 z$6V!S4sH@WPXY{$C%UM-<#c(c*km`W|M16*J0IO;M0vPHr2};}M7l*ZJ5+l_v^vx$ Ys&yz&RO!&3sNJEfvq)#b0!IdG05JoA2LJ#7 literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/elcl16/search_seqdiag_menu.gif b/org.eclipse.linuxtools.tmf.ui/icons/elcl16/search_seqdiag_menu.gif new file mode 100755 index 0000000000000000000000000000000000000000..6f9dfffda1821c4a3e56134a3d748afcaa8cbe7e GIT binary patch literal 601 zcmV-f0;c^(Nk%w1VGsZi0OkMy$;HOIx45jJr2qZ?v81c6qp8Ng#H^gBw5hSOr?0iE zv%0Xj%f`ya!NtJ4zqPBhsG6sznWnwAz5o9I!@8tnOpZw^p^3{*>*OK?&q4w2|`Q4uM)s)}8eX5UO zU8jgyn|@TU zk6@>WT&|B{tc_u;k7M$sJMpAE?XX?Em}at&VzZHBjBZMfa7>MFO^$F*pM6-AaZ!+M zPmpd>$jHe5|Nj6000000000000000000000000000000000000A^8LW004CWEC2ui z01yBW000NJfOUd`FN21IC@v}^9U3r)hAAs8gmsB3kRBf*k&|^=Zfa>xCoCj}nj%|teJR3zyOLK!T8XY6O96?7+O6Iy3kY#&W`|*3PXHGI4+^xg0!a!E8)76VAaFnqq$oOK n(7*sEfdB|>4nf&5qs9OlGU7yV1IfpX2{dTj$ni!Qq(A^WYQ!x@ literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/elcl16/select_menu.gif b/org.eclipse.linuxtools.tmf.ui/icons/elcl16/select_menu.gif new file mode 100755 index 0000000000000000000000000000000000000000..a939cf775d4f74287a85b1b0e365aa6fec6b4cf3 GIT binary patch literal 871 zcmb`GziO0G5XC2Iu|goR2neDfV7VG_L=-IqmxSO0XsHnfEKOtkfmR|)u$oQ+b{3YF zn^Kt42Z)_7ApU8_-?_em7nc2I@4aV!=ghaedpkF8JzlqU`)OL!_)Oz#qMEdp%VqO> zTQ}c)#*?1^<8rewOS4iZDtGfRPxCTwZH8NfMOu_a>ukDZSf*uJwyu_2g;iRWl}N4K zI;_*WV3)9@(c&_vTYa>`J>AQ_)s`y4BR$HaMZso%6K8)>v!$!VLKRhg|I@kd*#s;nkg>_Wk2c4deI);?3Ij!JS{9_P-onyl`~+`?DuIZ#S>p Tc>VO+hx_lYKe)6yv)g|GS=o3r literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/eview16/sequencediagram_view.gif b/org.eclipse.linuxtools.tmf.ui/icons/eview16/sequencediagram_view.gif new file mode 100755 index 0000000000000000000000000000000000000000..a024c49ba7f0dbbe268139327a2ca701f3cd8de4 GIT binary patch literal 225 zcmZ?wbhEHb6krfwIKseCP}Y`R)RbS=R#@I#Qq@*a(VAD*nAtKvt!8rBgyq|>ed^fv zD!YAt#l+>&Z7cHnmS?oh%j#HA*t4*1)`sfoYwBjMJ97Wa+|zGrrmw1#3WsFOxTN76q_^z%4}5> W;o;ybDbtW*(&NYquZUcHsQZLl+L5ymsX5t;1(-R&*|??pfS0VOjI!mCch^v`<^zJ!{>P z4F{HOI<#fqnG+Z97BS&av~iHtadMe)ow93pN}%f9pROFrWg( zpDc_F3|0&}AYqW77}!c2<`#J9NcFq9Bq=Nq5EJEITyl^ne{-M)L)J|j!E0QmE(|jL zZi<^P7AVZU958kD?pHr{W$9TxC{U_5syC^2(yS1!tT1p^V-uRdHhBu0nzKrUPLqV50y literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/obj16/arrow_colapse.bmp b/org.eclipse.linuxtools.tmf.ui/icons/obj16/arrow_colapse.bmp new file mode 100755 index 0000000000000000000000000000000000000000..fb5082b07a62086f57a92171c4324cdedf3e592f GIT binary patch literal 3078 zcmeH@F%H8Z3`OaZrClTC2pK!&0NH!@+^DrcLSHugBEuGt>==LkgZTU`uMpQEd6#%g ztP+o~Ne0$wQvhPs@bCQjek3Y zhLYl-#A{{bTaH3iaZuv5GV(1)p{h72ky^Q0wuFX~MWI@(t);P3q+!#+X|&XiIc~S8 Y46%}<(@#qT$JI5c1rlmy9%E(+&w&>WZm1j5Z zzkM`ldP2~oxVPWm`%RAWpBxoCC%t@qL;0%uu3eLkTsRgsJw9bYPW-&|xVdRn>l+)k zw$DDiAZB(_>cZ@}*-0sLGqM)v-FbC4eokV-+@!Sm86~UAsyEaWtto2S-njnay7G0U ztIx08eQQ_Ux~iM6ZXCOCv|@G9fm?g~cX!M^GJX5?O*?OHIdFT=p}YI$ADQvs{hbpR zjvhaMLhf!vZmZvL`GCE({C{Z3`55q$&=1 z%Q-WCc~I&6pntZBD4X5_p|k^%Mj1zD1O}&dcQ)!|i5Rl99%JJ(vU*dnFku0QoHNJE z4~36gC#&<+c}Qd|WEbXP>@iUc%IKP;rNI^#pvcNSmx+nz>4{A)%?eG1ats@bSkFw? nbI(yR=typzpb|KN<>Q1$4;MPHI~|ZnT+sLY@(O=;76xkoAvi3W literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/obj16/classobject_obj.gif b/org.eclipse.linuxtools.tmf.ui/icons/obj16/classobject_obj.gif new file mode 100755 index 0000000000000000000000000000000000000000..fbb8c360aed873fa519bc24d6e85981f7041e0b4 GIT binary patch literal 586 zcmV-Q0=4}|Nk%w1VGsZi0OkMy|Nj5ufq$%{{R2i+1O>Fdyu`LU7K<=flFbab!?`7-Rt3H{X{BXvs{~GR+?Sx_U&7pU~Q;yhq#Gtrf;Ii zqqESonZKBx$D5+apR?1h?DOl`>eSZg(AU}4*V@y>#JJ_<+uPgG|Ns5d(7~0CahQ&C z#=^Flj&kVT!I_P79m ziE!o7u;0(M-_N$}+`{$c%kbsX*U!SQk6y5lUy5~BoQrXqigWbk&Z?V+or!R(nue*G zhqbGpw5p!BtDm;1ptq@@xT>N5|NsC0{{R30000000000000000A^8LW004RbEC2ui z01yBW000N4fO>)(FDoP|f{BWH5;Hq9AAR!f>7b8q677r~Zo|GpZC^-~FHZgNvU0-#I4j(iuFfm(GPzy;>af%xm7%_KG z2MGiO15R_FY)uLV0tEGP-l;2krY!H9wz7Nr`tE6KdZw@a z^Y?$(v^AYmSNBX`-92qp*VL8W(^hm%UEVc)RVPr{jMd#UR{s6>pT)JB!L5qPwTjuT zn!%%r!Ly3Nt@6gF|5rc#zXn8~f#kodAOBwe{O`x#|JT0!zxw&#l}~@){`!9f#QyvG z*Z+6F|6l&{_tKZY*S`F@`uX?kUw_~H`uqOR|4)DZ{rva;-Jid|{{tQS|JT32sSB$o z>|GE$yU3)`J8?m2&a%3@Zy(vT1v>YHdrXY+=!@~0l#sTxtY_Qtva{KZ*ole`lib-u1C#Cov?M<%dfBF z=BH;aEtF>_ofn&V-bL!dQm%$pM8 z6PoOlC&Di%%F55l$k=4#8y^|NtgWrAuV|>q)9LB%7Q>>VWg?@ct0g?2&&b$R#z@bC zf4+daoTRjbtQpsQc1@p#6Ah1AIGE=P9f&A!cJ7q1z7m*pK#SUK5+ R3>+>v99TZNgo%;C8UX!@CmR3& literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/obj16/node_obj.gif b/org.eclipse.linuxtools.tmf.ui/icons/obj16/node_obj.gif new file mode 100755 index 0000000000000000000000000000000000000000..02b51a8f279a9da16f570a0dbae41327c8baa34c GIT binary patch literal 579 zcmZ?wbhEHb6krfwc*el+`RniRKYm;JH9CfMI7f7QB}@uPn-P{fFQI%zZ0WMJ`t>0x z&Ee^7-+ugw&hCuQ?@1|{@bKl2hcCZZOgj;k-H}-}HM4R`ar2y)?|;7i^sBsmetG-6 zhp&D-eD(d?kKfucbF`zTyA`jEs$XB!Ji81i+cCeaecppt-`{=y_4(T`&HN*pd51J} z_GxDC(#qPUnXy$XW2TFGm)QdVolFVqI2nAuuU)3u_fYe!En>zM!P+wZrZ ze|`J;=iATU=imPR|NlS3kOhiASr{1@92s;#J^;lD1N(vo$EN0%)@CKG&aQ4PrDkpJ ziIXOCYd7ogILw+on@6WvSH!_yj#18zSB{rYui1d#fs2!kg-O$ffnUGbP{2V$!}h=d zNdco~W6_OV9PF&jOcG)y&8Er@>VmQ|f wDSTFJQ?&f|@Ar?V97Y=y92r}jd=ySxc)-%)sP^Tz`^WhWt!+kJTr3RM0QlXhyZ`_I literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/obj16/package_obj.gif b/org.eclipse.linuxtools.tmf.ui/icons/obj16/package_obj.gif new file mode 100755 index 0000000000000000000000000000000000000000..773c301085b3323ba447c3021e2b0bad1539f040 GIT binary patch literal 574 zcmZ?wbhEHb6krfwc*el+{nPGWA9wxuu>beRJ+H4WdwX%wn~O`Ho|^yUB)spPAq(PaplvKi{4yVd2Pp}7bg~8 z-#PW!iAC3TPQSWi+V!2&ukDz2W7mw^`{vy}u;9X)j+;BCU)w(8`i_}*_AlHst?2yf z?sKcV&#vk^yQcT%j+xiC&)PSw_}uE=2E(H+HGcxd%UI2)M)oP@jmg^`ZJ<BwWPW@1^ULv#rp(!M|3uasq^q^MkqGzm literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/obj16/plus_obj.gif b/org.eclipse.linuxtools.tmf.ui/icons/obj16/plus_obj.gif new file mode 100755 index 0000000000000000000000000000000000000000..ebc00e32f22f5a9a521a55950a013f684093bb05 GIT binary patch literal 871 zcmb`Gy=s(U5QRsRV#O3&iYU0Pgrt~e#ljzg#v+7-U~e;Mo3s`yDEQL^v~a*GiG6IX z7Gfhz<3)H4Vl_VJy#@!CZ)a!b%sFSief;Fn{LZtfP3?~<7UQ!Rj|JI6v0N^j-`jdT zc=C2B>&gnPP|duuIOghzUmM~el;4A1l|&lV|qE49W_MlDw2 z2Q-<+4mcqgg|G$;(o8}N9xMhM=+=qUO_`i%E{2ilV=Y;YmrTBnd#gB3D4WMs)H{rp z>%%*Z4m)&&p-@PJv~DtZ2zWsAI&dY-LldiJe^)OVe}#3u)%s7|pKRG|a%21a)`NR1 zduvgM|&+uzQ9ew^;_9K1OGeer7J%J)7e5?3_u|y0*X#E_*?IIu@3aj& zk360-ciY|*&*m=Mzw5}u1E-$7{_y|J$Nv*%Za8@M)t=*z-hTSO^~lpL2kvY?cK^%I z{~vz-|MdI+mp}i%{{8>s|Nr0r|Ns5}f5p>dD<2(koRQ)(GnJuQ1d|OKI#(Z|=^lX^k$f4sB>pY3fX`Y>X(c3#)31tZIyG z>_}|s%4+G%JaKwwb4PMhd&=pvyZ`>5_wUc!|M%nm-B0*;Kk5Iyxc_(K_bqTZFyC(f zJja>+B?sm@&gw7zbHMK3e!Kts9seD4n%P@=XMx?Eo}&BnZST*wyEor1&tEy;PdVR5 zDbH6W*H&?lG z2)578(o4k~$69o_XeGF3)4uu?aKin2%aC3zrgEO;&xTGJWL9-hJBTGw5 zOvMo|34@qf4vw3QE(`c5$h2rAD!QLlR27nWp>WV)qED-mV^+oyPGK#dX*M@6FTW(= zUCX4a@~O$SOUyvzM`AErucT>>*tdX%2btNqIyB~H9)96F%d^YqWq{)&mM+1zXGeCr zs7;iynDS-aj|~ckTAllz+^BR>_gmsRvtY-94+mIUI2-O<+1Z@lyP(yYD@C9&fw|ef hS>mbU75DRp^u0FaTx?p}HN&S@&vTV$O`-#XH2|XNag+c6 literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/obj16/thread_obj.gif b/org.eclipse.linuxtools.tmf.ui/icons/obj16/thread_obj.gif new file mode 100755 index 0000000000000000000000000000000000000000..cade7851226b52f5f551e0c53d04b5982bcfab85 GIT binary patch literal 538 zcmZ?wbhEHb6krfwc*el+@BhYs|2O~txB377Ex*66{P%UmzptzQeqHtL<*aWnXaD>2 z;r)${54U?h-s<^qv-iub314pXZCKX(=0w%oleO!Y^sQUmzjn#w$D2}L?aqC*EAROJ zO(za)ezH0J@y3kTyYgP`D(Eis=`IdDwtwsK{ad;V1G)->dkTZQ3xZGW+i-S9N^e2X z>HV8e?Avl`&*rj((9*<+l7#TJZKWI9%GS4*u4}I-PlzZ@j4DZp+|X9BvAuF_d)3C) zicM{mo7$>3wpNuVM3u!ymc&Pu$3>RMN0-G#{r~@;VMqbRpDc_F46Y11AXkIpgn_-h z!L_NmrM0cSquJ2jy3^IjMZ=oOM90*wU7u0ijM-k5!K__bft{U=g;hnV&DzMBQ&eA! z!@=0L)qq=oPoK-4ho4uk)xy$ISj0?F$jri|O-fN-QbJx%O}E`%R?A$*UR~Uv-O5SZ VMAKT@+@{asT;He9Upg8YtN}+3r}h8< literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/obj16/zoom_mask.bmp b/org.eclipse.linuxtools.tmf.ui/icons/obj16/zoom_mask.bmp new file mode 100755 index 0000000000000000000000000000000000000000..22eb40d4a10b04fe828c718073551d3bd0ac50a6 GIT binary patch literal 190 rcmZ?r-Nyg{c0fu4h!voik%1A&Vqja)KY;in5I_F^ t|AYJg|L6Jt|KETA|Noum|NpOi|NnnNJ_Lia)KY;in5I_F^ o|AYJg|L6Jt|KEQfh|mB3U;qC9|Ac%92C>27K)LhwK(#<{9 literal 0 HcmV?d00001 diff --git a/org.eclipse.linuxtools.tmf.ui/icons/ovr16/error_ovr.gif b/org.eclipse.linuxtools.tmf.ui/icons/ovr16/error_ovr.gif new file mode 100755 index 0000000000000000000000000000000000000000..119dcccd5a7dce3fef6029bdaa5017cbacfa35ab GIT binary patch literal 82 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 O0000 + + + + @@ -245,6 +255,97 @@ id="org.eclipse.linuxtools.tmf.ui.command.managecustomparsers" name="%commands.parser.manage"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -455,5 +556,36 @@ class="org.eclipse.linuxtools.tmf.ui.parsers.custom.CustomParserProvider"> + + + + + + + + + + + + + + diff --git a/org.eclipse.linuxtools.tmf.ui/pom.xml b/org.eclipse.linuxtools.tmf.ui/pom.xml index 78edce81fa..21baaea8d4 100644 --- a/org.eclipse.linuxtools.tmf.ui/pom.xml +++ b/org.eclipse.linuxtools.tmf.ui/pom.xml @@ -18,7 +18,7 @@ org.eclipse.linuxtools.tmf.ui - 0.3.1-SNAPSHOT + 0.3.2-SNAPSHOT eclipse-plugin Linux Tools TMF UI (LTTng) Plug-in diff --git a/org.eclipse.linuxtools.tmf.ui/schema/org.eclipse.linuxtools.tmf.ui.parserprovider.exsd b/org.eclipse.linuxtools.tmf.ui/schema/org.eclipse.linuxtools.tmf.ui.parserprovider.exsd index 870ee88446..89cc53afc5 100644 --- a/org.eclipse.linuxtools.tmf.ui/schema/org.eclipse.linuxtools.tmf.ui.parserprovider.exsd +++ b/org.eclipse.linuxtools.tmf.ui/schema/org.eclipse.linuxtools.tmf.ui.parserprovider.exsd @@ -1,102 +1,102 @@ - - - - - - - - - A parser provider registers a list of trace parsers to the tracing framework. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [Enter the first release in which this extension point appears.] - - - - - - - - - [Enter extension point usage example here.] - - - - - - - - - [Enter API information here.] - - - - - - - - - [Enter information about supplied implementation of this extension point.] - - - - - + + + + + + + + + A parser provider registers a list of trace parsers to the tracing framework. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [Enter the first release in which this extension point appears.] + + + + + + + + + [Enter extension point usage example here.] + + + + + + + + + [Enter API information here.] + + + + + + + + + [Enter information about supplied implementation of this extension point.] + + + + + diff --git a/org.eclipse.linuxtools.tmf.ui/schema/org.eclipse.linuxtools.tmf.ui.uml2SDLoader.exsd b/org.eclipse.linuxtools.tmf.ui/schema/org.eclipse.linuxtools.tmf.ui.uml2SDLoader.exsd new file mode 100644 index 0000000000..add2705bc6 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/schema/org.eclipse.linuxtools.tmf.ui.uml2SDLoader.exsd @@ -0,0 +1,193 @@ + + + + + + + + + This extension point aims to list and connect any UML2 Sequence Diagram loader. + + + + + + + + + + + + + + + + + A fully qualified identifier of the target extension point. + + + + + + + An optional identifier of the extension instance. + + + + + + + An optional name of the extension instance. + + + + + + + + + + + + A unique identifier for this <code>uml2SDLoader</code>. This is not mandatory as long as the id attribute cannot be retrieved by the provider plugin. The class attribute is the one on which the underlying algorythm relies. + + + + + + + An name of the extension instance. + + + + + + + The implementation of this UML2 SD viewer loader. The class must implement <code>org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.IUml2SDLoader</code>. + + + + + + + + + + The view ID of the view that this loader aims to populate. +Either <code>org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView</code> itself or a extension of <code>org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView</code>. + + + + + + + Set to true to make this loader the default one for the view; in case of several default loaders, first one coming from extensions list is taken. + + + + + + + + + + + + Since 0.4.0 (based on UML2SD of org.eclipse.tptp.common.ui) + + + + + + + + + <p> +For an example implemetation of the loader class see: +<pre> +plug-in: org.eclipse.linuxtools.tmf.ui +package: org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl +class: TmfUml2SDSyncLoader +</pre> +</p> + +<p> +The following is an example of the extension point: +<pre> +<extension + point="org.eclipse.linuxtools.tmf.ui.uml2SDLoader"> + <uml2SDLoader + class="org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl.TmfUml2SDSyncLoader" + default="true" + id="org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl.TmfUml2SDSyncLoader" + name="Component Interactions" + view="org.eclipse.linuxtools.ust.examples.ui.componentinteraction"> + </uml2SDLoader> +</extension> +</pre> +</p> +<p> +The following is an example of the extension point for the required sequence diagram view: +<pre> +<extension + point="org.eclipse.ui.views"> + <view + category="org.eclipse.linuxtools.ust.examples.ui.sequenceDiagrams" + class ="org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView" + id="org.eclipse.linuxtools.ust.examples.ui.componentinteraction" + name="Sequence Diagram" + restorable="true"> + </view> +</extension> +</pre> +</p> + +<p> +The following example command handler shows how to specify which loader to use while opening the sequence diagram view. This is not necessary if there is only one loader for a given sequence diagram view associated and this loader has the attribute "default" set to "true". +<pre> +public class OpenSDView extends AbstractHandler { + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + try { + IWorkbenchPage persp = TmfUiPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getActivePage(); + SDView view = (SDView) persp.showView("org.eclipse.linuxtools.ust.examples.ui.componentinteraction"); + LoadersManager.getLoadersManager().createLoader("org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl.TmfUml2SDSyncLoader", view); + } catch (PartInitException e) { + throw new ExecutionException("PartInitException caught: ", e); + } + return null; + } +} +</pre> +</p> + + + + + + + + + <p> +For this extension point a sequence diagram view has to be defined as well. The sequence diagram view class implementation is provided by the plug-in org.eclipse.linuxtools.tmf.ui (org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView) and can be used as is or can also be sub-classed. +</p> +<p> +With this extension point, a loader class is associated with a sequence diagram view. Multiple loaders can be associated to a single sequence diagram view. However, additional means have to be implemented to specify which loader should be used when opening the view. For example, an eclipse action or command could be used for that (see example section). This additional code is not necessary if there is only one loader for a given sequence diagram view associated and this loader has the attribute "default" set to "true". +</p> + + + + + + + + + + Copyright (c) 2005, 2008, 2011 IBM Corporation, Ericsson and others + +All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at <a +href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a> + + + + diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/ITmfImageConstants.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/ITmfImageConstants.java new file mode 100755 index 0000000000..f3b7963eef --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/ITmfImageConstants.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: CommonUIImages.java,v 1.6 2010/05/05 12:32:31 paules Exp $ + * + * Contributors: + * Bernd Hufmann - initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui; + +@SuppressWarnings("nls") +public interface ITmfImageConstants { + + public static final String ICONS_PATH = "icons/"; //$NON-NLS-1$ + + /* elcl16 */ + public static final String IMG_UI_HOME_MENU = ICONS_PATH + "elcl16/home_nav.gif"; + public static final String IMG_UI_SELECT_MENU = ICONS_PATH + "elcl16/select_menu.gif"; + public static final String IMG_UI_ZOOM_IN_MENU = ICONS_PATH + "elcl16/zoomin_nav.gif"; + public static final String IMG_UI_ZOOM_OUT_MENU = ICONS_PATH + "elcl16/zoomout_nav.gif"; + public static final String IMG_UI_FILTERS = ICONS_PATH + "elcl16/filter_items.gif"; + public static final String IMG_UI_SEARCH_SEQ = ICONS_PATH + "elcl16/search_seqdiag_menu.gif"; + public static final String IMG_UI_NEXT_PAGE = ICONS_PATH + "elcl16/next_menu.gif"; + public static final String IMG_UI_PREV_PAGE = ICONS_PATH + "elcl16/prev_menu.gif"; + public static final String IMG_UI_GOTO_PAGE = ICONS_PATH + "elcl16/gotopage_menu.gif"; + public static final String IMG_UI_NODE_START = ICONS_PATH + "elcl16/node_end.gif"; + public static final String IMG_UI_NODE_END = ICONS_PATH + "elcl16/node_start.gif"; + public static final String IMG_UI_SEARCH_MATCH = ICONS_PATH + "elcl16/search_match.gif"; + public static final String IMG_UI_FIRST_PAGE = ICONS_PATH + "elcl16/backward_nav.gif"; + public static final String IMG_UI_LAST_PAGE = ICONS_PATH + "elcl16/forward_nav.gif"; + + /* eview16 */ + public static final String IMG_UI_SEQ_DIAGRAM_OBJ = ICONS_PATH + "eview16/sequencediagram_view.gif"; + + /* obj16 */ + public static final String IMG_UI_ZOOM = ICONS_PATH + "obj16/zoom_mask.bmp"; + public static final String IMG_UI_ZOOM_IN = ICONS_PATH + "obj16/zoomin_obj.bmp"; + public static final String IMG_UI_ZOOM_OUT = ICONS_PATH + "obj16/zoomout_obj.bmp"; + public static final String IMG_UI_ARROW_COLLAPSE_OBJ = ICONS_PATH + "obj16/arrow_colapse.bmp"; + public static final String IMG_UI_ARROW_UP_OBJ = ICONS_PATH + "obj16/arrow_up.bmp"; +} \ No newline at end of file diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/TmfUiPlugin.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/TmfUiPlugin.java index 0c94663289..ab502899a1 100644 --- a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/TmfUiPlugin.java +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/TmfUiPlugin.java @@ -12,7 +12,11 @@ package org.eclipse.linuxtools.tmf.ui; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.ImageRegistry; +import org.eclipse.linuxtools.tmf.ui.TmfUiTracer; import org.eclipse.linuxtools.tmf.ui.parsers.ParserProviderManager; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.LoadersManager; import org.eclipse.swt.graphics.Image; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.osgi.framework.BundleContext; @@ -69,6 +73,7 @@ public class TmfUiPlugin extends AbstractUIPlugin { public void start(BundleContext context) throws Exception { super.start(context); plugin = this; + TmfUiTracer.init(); ParserProviderManager.init(); } @@ -78,13 +83,41 @@ public class TmfUiPlugin extends AbstractUIPlugin { */ @Override public void stop(BundleContext context) throws Exception { + TmfUiTracer.stop(); plugin = null; super.stop(context); } - public Image getImageFromPath(String path) - { - return AbstractUIPlugin.imageDescriptorFromPlugin(PLUGIN_ID, path).createImage(); + public Image getImageFromPath(String path){ + return getImageDescripterFromPath(path).createImage(); + } + + public ImageDescriptor getImageDescripterFromPath(String path){ + return AbstractUIPlugin.imageDescriptorFromPlugin(PLUGIN_ID, path); + } + + public Image getImageFromImageRegistry(String path) { + Image icon = getImageRegistry().get(path); + if (icon == null) { + icon = getImageDescripterFromPath(path).createImage(); + plugin.getImageRegistry().put(path, icon); + } + return icon; } + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#initializeImageRegistry(org.eclipse.jface.resource.ImageRegistry) + */ + @Override + protected void initializeImageRegistry(ImageRegistry reg) { + reg.put(ITmfImageConstants.IMG_UI_ZOOM, getImageFromPath(ITmfImageConstants.IMG_UI_ZOOM)); + reg.put(ITmfImageConstants.IMG_UI_ZOOM_IN, getImageFromPath(ITmfImageConstants.IMG_UI_ZOOM_IN)); + reg.put(ITmfImageConstants.IMG_UI_ZOOM_OUT, getImageFromPath(ITmfImageConstants.IMG_UI_ZOOM_OUT)); + reg.put(ITmfImageConstants.IMG_UI_SEQ_DIAGRAM_OBJ, getImageFromPath(ITmfImageConstants.IMG_UI_SEQ_DIAGRAM_OBJ)); + reg.put(ITmfImageConstants.IMG_UI_ARROW_COLLAPSE_OBJ, getImageFromPath(ITmfImageConstants.IMG_UI_ARROW_COLLAPSE_OBJ)); + reg.put(ITmfImageConstants.IMG_UI_ARROW_UP_OBJ, getImageFromPath(ITmfImageConstants.IMG_UI_ARROW_UP_OBJ)); + } + + } diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/TmfUiTracer.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/TmfUiTracer.java new file mode 100644 index 0000000000..fc86fd2422 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/TmfUiTracer.java @@ -0,0 +1,164 @@ +package org.eclipse.linuxtools.tmf.ui; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; + +import org.eclipse.core.runtime.Platform; + +@SuppressWarnings("nls") +public class TmfUiTracer { + + private static String pluginID = TmfUiPlugin.PLUGIN_ID; + + static Boolean ERROR = Boolean.FALSE; + static Boolean WARNING = Boolean.FALSE; + static Boolean INFO = Boolean.FALSE; + + static Boolean INDEX = Boolean.FALSE; + static Boolean DISPLAY = Boolean.FALSE; + static Boolean SORTING = Boolean.FALSE; + + private static String LOGNAME = "traceUI.log"; + private static BufferedWriter fTraceLog = null; + + private static BufferedWriter openLogFile(String filename) { + BufferedWriter outfile = null; + try { + outfile = new BufferedWriter(new FileWriter(filename)); + } catch (IOException e) { + e.printStackTrace(); + } + return outfile; + } + + public static void init() { + + String traceKey; + boolean isTracing = false; + + traceKey = Platform.getDebugOption(pluginID + "/error"); + if (traceKey != null) { + ERROR = (Boolean.valueOf(traceKey)).booleanValue(); + isTracing |= ERROR; + } + + traceKey = Platform.getDebugOption(pluginID + "/warning"); + if (traceKey != null) { + WARNING = (Boolean.valueOf(traceKey)).booleanValue(); + isTracing |= WARNING; + } + + traceKey = Platform.getDebugOption(pluginID + "/info"); + if (traceKey != null) { + INFO = (Boolean.valueOf(traceKey)).booleanValue(); + isTracing |= INFO; + } + + traceKey = Platform.getDebugOption(pluginID + "/updateindex"); + if (traceKey != null) { + INDEX = (Boolean.valueOf(traceKey)).booleanValue(); + isTracing |= INDEX; + } + + traceKey = Platform.getDebugOption(pluginID + "/display"); + if (traceKey != null) { + DISPLAY = (Boolean.valueOf(traceKey)).booleanValue(); + isTracing |= DISPLAY; + } + + traceKey = Platform.getDebugOption(pluginID + "/sorting"); + if (traceKey != null) { + SORTING = (Boolean.valueOf(traceKey)).booleanValue(); + isTracing |= SORTING; + } + + // Create trace log file if needed + if (isTracing) { + fTraceLog = openLogFile(LOGNAME); + } + } + + public static void stop() { + if (fTraceLog == null) + return; + + try { + fTraceLog.close(); + fTraceLog = null; + } catch (IOException e) { + e.printStackTrace(); + } + } + + // Predicates + public static boolean isErrorTraced() { + return ERROR; + } + + public static boolean isIndexTraced() { + return INDEX; + } + + public static boolean isDisplayTraced() { + return DISPLAY; + } + + public static boolean isSortingTraced() { + return SORTING; + } + + // Tracers + public static void trace(String msg) { + long currentTime = System.currentTimeMillis(); + StringBuilder message = new StringBuilder("["); + message.append(currentTime / 1000); + message.append("."); + message.append(String.format("%1$03d", currentTime % 1000)); + message.append("] "); + message.append(msg); + + if (fTraceLog != null) { + try { + fTraceLog.write(message.toString()); + fTraceLog.newLine(); + fTraceLog.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static void traceIndex(String msg) { + String message = ("[INDEX] " + msg); + trace(message); + } + + public static void traceDisplay(String msg) { + String message = ("[DISPLAY]" + msg); + trace(message); + } + + public static void traceSorting(String msg) { + String message = ("[SORT] " + msg); + trace(message); + } + + public static void traceError(String msg) { + String message = ("[ERR] Thread=" + Thread.currentThread().getId() + " " + msg); + trace(message); + } + + public static void traceWarning(String msg) { + String message = ("[WARN] Thread=" + Thread.currentThread().getId() + " " + msg); + trace(message); + } + + public static void traceInfo(String msg) { + String message = ("[INF] Thread=" + Thread.currentThread().getId() + " " + msg); + trace(message); + } + + + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/preferences/TmfTracingPreferencePage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/preferences/TmfTracingPreferencePage.java new file mode 100644 index 0000000000..12c1df7b7b --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/preferences/TmfTracingPreferencePage.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + ******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.preferences; + +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; + +/** + * TmfTracingPreferencePage + * Top Level Preference Page for TMF. + *

+ */ +public class TmfTracingPreferencePage extends PreferencePage implements IWorkbenchPreferencePage { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + + /* + * (non-Javadoc) + * @see org.eclipse.jface.preference.PreferencePage#createContents(org.eclipse.swt.widgets.Composite) + */ + @Override + protected Control createContents(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + // No content yet! + return composite; + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench) + */ + @Override + public void init(IWorkbench workbench) { + // Nothing to do yet! + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/DiagramToolTip.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/DiagramToolTip.java new file mode 100755 index 0000000000..5d94def0b7 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/DiagramToolTip.java @@ -0,0 +1,109 @@ +/********************************************************************** + * Copyright (c) 2005, 2007, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: DiagramToolTip.java,v 1.3 2007/01/02 21:11:39 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.FontMetrics; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * This class is used to reproduce the same tooltip behavior on Windows and Linux when the mouse hovers over the + * sequence diagram + * + * @author sveyrier + */ +public class DiagramToolTip { + + /** + * The parent control where the tooltip must be drawn + */ + protected Control parent = null; + /** + * The tooltip shell + */ + protected Shell toolTipShell = null; + /** + * The tooltip text + */ + protected String text = null; + protected Text textBox = null; + + // added for defect 141750 in order to allow proper text wrapping of the toolTip + // E. Dancy + + /** + * Create a new tooltip for the given parent control + * + * @param _parent the parent control + */ + public DiagramToolTip(Control _parent) { + parent = _parent; + toolTipShell = new Shell(parent.getShell(), SWT.MULTI); + toolTipShell.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + textBox = new Text(toolTipShell, SWT.WRAP | SWT.MULTI); + textBox.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + } + + /** + * Display the tooltip using the given text The tooltip will stay on screen until it is told otherwise + * + * @param value the text to display + */ + public void showToolTip(String value) { + if ((value == null) || (value.equalsIgnoreCase("")))//$NON-NLS-1$ + { + toolTipShell.setVisible(false); + return; + } + + text = value; + int w = toolTipShell.getBounds().width; + Point hr = Display.getDefault().getCursorLocation(); + int cursorH = 32; + for (int i = 0; i < Display.getDefault().getCursorSizes().length; i++) { + if (Display.getDefault().getCursorSizes()[i].y < cursorH) + cursorH = Display.getDefault().getCursorSizes()[i].y; + } + if (hr.x + w > Display.getDefault().getBounds().width) { + int tempX = (hr.x + w) - Display.getDefault().getBounds().width; + if (tempX > Display.getDefault().getBounds().width) + hr.x = 0; + hr.x = hr.x - tempX; + } + textBox.setText(value); + int charactersPerColumn = 100; + GC gc = new GC(textBox); + FontMetrics fm = gc.getFontMetrics(); + gc.dispose(); + int width = charactersPerColumn * fm.getAverageCharWidth(); + textBox.setSize(textBox.computeSize(width, textBox.getLineCount() * textBox.getLineHeight())); + toolTipShell.setLocation(hr.x, hr.y + cursorH); + toolTipShell.setSize(textBox.getSize()); + textBox.setVisible(true); + toolTipShell.setVisible(true); + + } + + /** + * Hide the tooltip + */ + public void hideToolTip() { + toolTipShell.setVisible(false); + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/DrawableToolTip.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/DrawableToolTip.java new file mode 100755 index 0000000000..c972a2b383 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/DrawableToolTip.java @@ -0,0 +1,180 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: DrawableToolTip.java,v 1.3 2008/01/24 02:29:01 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd; + +import org.eclipse.linuxtools.tmf.event.TmfTimeRange; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +/** + * This class is used to reproduce the same tooltip behavior on Windows and Linux when the mouse move hover the time + * compression bar used to display elapsed time using a tooltip. The tooltip is composed of 2 parts, the text value and + * below, a scale to visually locate the value in a time range (usually the min an max elapsed time in the whole + * diagram) + * + * @author sveyrier + */ +public class DrawableToolTip implements PaintListener { + + /** + * The parent control where the tooltip must be drawn + */ + protected Composite parent = null; + /** + * The tooltip shell + */ + protected Shell toolTipShell = null; + /** + * Time range data + */ + protected TmfTimeRange minMaxRange; + protected TmfTimestamp currentValue; + + protected static int H_MARGIN = 10; + protected static int V_MARGIN = 10; + protected static int TEXT_SCALE_MARGIN = 20; + protected static int SCALE_LENGTH = 100; + + protected String msg; + + /** + * The color array used to represent the 10 time range slices + */ + protected Color[] col; + + public DrawableToolTip(Composite _parent) { + parent = _parent; + toolTipShell = new Shell(parent.getShell(), SWT.ON_TOP); + toolTipShell.setLayout(new RowLayout()); + toolTipShell.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + toolTipShell.addPaintListener(this); + toolTipShell.setSize(200, 50); + + col = new Color[10]; + col[0] = new Color(Display.getDefault(), 255, 229, 229); + col[1] = new Color(Display.getDefault(), 255, 204, 204); + col[2] = new Color(Display.getDefault(), 255, 178, 178); + col[3] = new Color(Display.getDefault(), 255, 153, 153); + col[4] = new Color(Display.getDefault(), 255, 127, 127); + col[5] = new Color(Display.getDefault(), 255, 102, 102); + col[6] = new Color(Display.getDefault(), 255, 76, 76); + col[7] = new Color(Display.getDefault(), 255, 51, 51); + col[8] = new Color(Display.getDefault(), 255, 25, 25); + col[9] = new Color(Display.getDefault(), 255, 0, 0); + } + + /** + * Display the tooltip using the given time range(min,max) the current value and the time unit The tooltip will stay + * on screen until it is told otherwise + * + * @param _value the current in the scale + * @param _min the scale min + * @param _max the scale max + * @param unit the scale unit + */ + public void showToolTip(TmfTimestamp _value, TmfTimestamp min, TmfTimestamp max) { + minMaxRange = new TmfTimeRange(min.clone(), max.clone()); + currentValue = _value.clone(); + + int w = toolTipShell.getBounds().width; + int h = toolTipShell.getBounds().height; + Point hr = Display.getDefault().getCursorLocation(); + toolTipShell.setBounds(hr.x, hr.y + 26, w, h); + toolTipShell.setVisible(true); + } + + /** + * Hide the tooltip + */ + public void hideToolTip() { + toolTipShell.setVisible(false); + } + + /** + * Draw the tooltip text on the control widget when a paint event is received + */ + @Override + public void paintControl(PaintEvent event) { + msg = SDMessages._138 + " " + currentValue.toString(); //$NON-NLS-1$ + Point size = event.gc.textExtent(msg); + if (size.x < SCALE_LENGTH) + size.x = SCALE_LENGTH; + event.gc.drawText(msg, H_MARGIN, V_MARGIN, true); + event.gc.drawLine(H_MARGIN, V_MARGIN + TEXT_SCALE_MARGIN + size.y, H_MARGIN + SCALE_LENGTH, V_MARGIN + TEXT_SCALE_MARGIN + size.y); + + int step = SCALE_LENGTH / 10; + + // double gr = (max - min) / 10; + TmfTimestamp minMaxdelta = minMaxRange.getEndTime().getDelta(minMaxRange.getStartTime()); + double gr = (minMaxdelta.getValue()) / (double) 10; + + // double delta = currentValue-min; + TmfTimestamp delta = currentValue.getDelta(minMaxRange.getStartTime()); + long absDelta = Math.abs(delta.getValue()); + + int colIndex = 0; + if (gr != 0) { + // colIndex = Math.round((float)(Math.log(1+delta)/gr)); + colIndex = Math.round((float) (absDelta / gr)); + if (colIndex > col.length) + colIndex = col.length; + else if (colIndex <= 1) + colIndex = 1; + } else + colIndex = 1; + + for (int i = 0; i <= 10; i++) { + if (i < 10) + event.gc.setBackground(col[i]); + if ((i < colIndex) && (i < 10)) + event.gc.fillRectangle(H_MARGIN + i * step, V_MARGIN + TEXT_SCALE_MARGIN + size.y - 5, step, 11); + if (i == 0) + event.gc.drawText(SDMessages._56, H_MARGIN, size.y + 2 * V_MARGIN + TEXT_SCALE_MARGIN, true); + if (i == 0) { + int len = event.gc.textExtent(SDMessages._55).x; + event.gc.drawText(SDMessages._55, H_MARGIN + SCALE_LENGTH - len + 1, size.y + 2 * V_MARGIN + TEXT_SCALE_MARGIN, true); + } + int lineWidth = 10; + if ((i == 0) || (i == 10)) + lineWidth = 14; + event.gc.drawLine(H_MARGIN + i * step, V_MARGIN + TEXT_SCALE_MARGIN + size.y - lineWidth / 2, H_MARGIN + i * step, V_MARGIN + TEXT_SCALE_MARGIN + size.y + lineWidth / 2); + } + toolTipShell.setSize(size.x + 2 * H_MARGIN + 2, 2 * size.y + 3 * V_MARGIN + TEXT_SCALE_MARGIN); + } + + public String getText() { + return msg; + } + + public String getAccessibleText() { + return currentValue.toString(); + } + + /** + * Dispose the system resource used by this kind of toolTips (a colors array essentially) + * + */ + public void dispose() { + for (int i = 0; i < col.length; i++) + col[i].dispose(); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/ITimeCompressionListener.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/ITimeCompressionListener.java new file mode 100755 index 0000000000..519da066f7 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/ITimeCompressionListener.java @@ -0,0 +1,26 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ITimeCompressionListener.java,v 1.2 2006/09/20 20:56:25 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IColor; + +/** + * @author sveyrier + * + */ +public interface ITimeCompressionListener { + + public void deltaSelected(Lifeline lifeline, int startEvent, int nbEvent, IColor color); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/NGC.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/NGC.java new file mode 100755 index 0000000000..c858bfd297 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/NGC.java @@ -0,0 +1,688 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: NGC.java,v 1.3 2008/01/24 02:29:01 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Frame; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IColor; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IFont; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IImage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.impl.ColorImpl; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.preferences.SDViewPref; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Display; + +/** + * @author sveyrier + * + */ +public class NGC implements IGC { + + protected GC context; + protected SDWidget view; + protected Font tempFont = null; + protected IColor gradientColor = null; + protected IColor backGround = null; + protected IColor foreGround = null; + protected int y_; + protected int x_; + protected int yx; + protected int xx; + protected boolean drawWithFocus = false; + + protected static int vscreen_bounds = 0; + + public NGC(SDWidget scrollView, GC gc) { + context = gc; + view = scrollView; + } + + @Override + public void setLineStyle(int style) { + context.setLineStyle(style); + } + + @Override + public int getLineStyle() { + return context.getLineStyle(); + } + + @Override + public int getContentsX() { + return Math.round(view.getContentsX() / view.zoomValue); + } + + @Override + public int getContentsY() { + return Math.round(view.getContentsY() / view.zoomValue); + } + + @Override + public int getVisibleWidth() { + return Math.round(view.getVisibleWidth() / view.zoomValue); + } + + @Override + public int getVisibleHeight() { + return Math.round(view.getVisibleHeight() / view.zoomValue); + } + + @Override + public int contentsToViewX(int x) { + return view.contentsToViewX(x); + } + + @Override + public int contentsToViewY(int y) { + return view.contentsToViewY(y); + } + + protected byte code(int x, int y) { + byte c = 0; + y_ = vscreen_bounds; + x_ = vscreen_bounds; + yx = view.getVisibleHeight() + vscreen_bounds; + xx = view.getVisibleWidth() + vscreen_bounds; + if (y > yx) + c |= 0x01; // top + else if (y < y_) + c |= 0x02; // bottom + if (x > xx) + c |= 0x04; // right + else if (x < x_) + c |= 0x08; // left + return c; + } + + @Override + public void drawLine(int x1, int y1, int x2, int y2) { + x1 = Math.round(x1 * view.zoomValue); + y1 = Math.round(y1 * view.zoomValue); + x2 = Math.round(x2 * view.zoomValue); + y2 = Math.round(y2 * view.zoomValue); + x1 = view.contentsToViewX(x1); + y1 = view.contentsToViewY(y1); + x2 = view.contentsToViewX(x2); + y2 = view.contentsToViewY(y2); + + byte code1 = code(x1, y1); + byte code2 = code(x2, y2); + byte codex; + boolean draw = false; + boolean end = false; + int x = 0, y = 0; + + do { + if (code1 == 0 && code2 == 0) { + draw = true; + end = true; + } else if ((code1 & code2) != 0) { + end = true; + } else { + codex = (code1 != 0) ? code1 : code2; + if ((codex & 0x01) != 0) // top + { + x = x1 + ((x2 - x1) * (yx - y1)) / (y2 - y1); + y = yx; + } else if ((codex & 0x02) != 0) // bottom + { + x = x1 + ((x2 - x1) * (y_ - y1)) / (y2 - y1); + y = y_; + } else if ((codex & 0x04) != 0) // right + { + y = y1 + ((y2 - y1) * (xx - x1)) / (x2 - x1); + x = xx; + } else if ((codex & 0x08) != 0) // left + { + y = y1 + ((y2 - y1) * (x_ - x1)) / (x2 - x1); + x = x_; + } + + if (codex == code1) { + x1 = x; + y1 = y; + code1 = code(x1, y1); + } else { + x2 = x; + y2 = y; + code2 = code(x2, y2); + } + } + } while (!end); + if (draw) { + context.drawLine(x1, y1, x2, y2); + } + } + + @Override + public void drawRectangle(int x, int y, int width, int height) { + x = Math.round(x * view.zoomValue); + // Workaround to avoid problems for some special cases (not very nice) + if (y != getContentsY()) { + y = Math.round(y * view.zoomValue); + y = view.contentsToViewY(y); + } else + y = 0; + width = Math.round(width * view.zoomValue); + height = Math.round(height * view.zoomValue); + x = view.contentsToViewX(x); + + if (x < -vscreen_bounds) { + width = width + x + vscreen_bounds; + x = -vscreen_bounds; + } + if (y < -vscreen_bounds) { + height = height + y + vscreen_bounds; + y = -vscreen_bounds; + } + if ((width < -vscreen_bounds) && (x + width < -vscreen_bounds)) + width = -vscreen_bounds; + else if (width + x > view.getVisibleWidth() + vscreen_bounds) + width = view.getVisibleWidth() + vscreen_bounds - x; + if ((height < -vscreen_bounds) && (y + height < -vscreen_bounds)) + height = -vscreen_bounds; + else if (height + y > view.getVisibleHeight() + vscreen_bounds) + height = view.getVisibleHeight() + vscreen_bounds - y; + context.drawRectangle(x, y, width, height); + } + + @Override + public void drawFocus(int x, int y, int width, int height) { + IColor bC = getBackground(); + IColor fC = getForeground(); + + if (width < 0) { + x = x + width; + width = -width; + } + + if (height < 0) { + y = y + height; + height = -height; + } + + x = Math.round(x * view.zoomValue); + y = Math.round(y * view.zoomValue); + width = Math.round(width * view.zoomValue); + height = Math.round(height * view.zoomValue); + + setForeground(Frame.getUserPref().getForeGroundColorSelection()); + setBackground(Frame.getUserPref().getBackGroundColorSelection()); + + context.drawFocus(view.contentsToViewX(x - 1), view.contentsToViewY(y - 1), width + 3, height + 3); + + setBackground(bC); + setForeground(fC); + } + + @Override + public void fillPolygon(int[] points) { + int len = (points.length / 2) * 2; + int[] localPoint = new int[len]; + for (int i = 0; i < len; i++) { + localPoint[i] = view.contentsToViewX(Math.round(points[i] * view.zoomValue)); + i++; + localPoint[i] = view.contentsToViewY(Math.round(points[i] * view.zoomValue)); + } + + if (validatePolygonHeight(localPoint) <= 0) + return; + + context.fillPolygon(localPoint); + } + + @Override + public void drawPolygon(int[] points) { + int len = (points.length / 2) * 2; + int[] localPoint = new int[len]; + for (int i = 0; i < len; i++) { + localPoint[i] = view.contentsToViewX(Math.round(points[i] * view.zoomValue)); + i++; + localPoint[i] = view.contentsToViewY(Math.round(points[i] * view.zoomValue)); + } + + if (validatePolygonHeight(localPoint) <= 0) + return; + + context.drawPolygon(localPoint); + } + + private int validatePolygonHeight(int[] localPoint) { + int i = 1; + int max = 0; + int min = Integer.MAX_VALUE; + while (i < localPoint.length) { + max = Math.abs(localPoint[i]) > Math.abs(max) ? localPoint[i] : max; + min = Math.abs(localPoint[i]) < Math.abs(min) ? localPoint[i] : min; + i+=2; + } + int height = max - min; + if (min < -vscreen_bounds) { + height = height + min + vscreen_bounds; + min = -vscreen_bounds; + } + if ((height < -vscreen_bounds) && (min + height < -vscreen_bounds)) + height = -vscreen_bounds; + + else if (height + min > view.getVisibleHeight() + vscreen_bounds) + height = view.getVisibleHeight() + vscreen_bounds - min; + return height; + } + + @Override + public void fillRectangle(int x, int y, int width, int height) { + x = Math.round(x * view.zoomValue); + // Workaround to avoid problems for some special cases (not very nice) + if (y != getContentsY()) { + y = Math.round(y * view.zoomValue); + y = view.contentsToViewY(y) + 1; + } else + y = 1; + width = Math.round(width * view.zoomValue) - 1; + height = Math.round(height * view.zoomValue) - 1; + x = view.contentsToViewX(x) + 1; + if (x < -vscreen_bounds) { + width = width + x + vscreen_bounds; + x = -vscreen_bounds; + } + if (y < -vscreen_bounds) { + height = height + y + vscreen_bounds; + y = -vscreen_bounds; + } + if ((width < -vscreen_bounds) && (x + width < -vscreen_bounds)) + width = -vscreen_bounds; + else if (width + x > view.getVisibleWidth() + vscreen_bounds) + width = view.getVisibleWidth() + vscreen_bounds - x; + if ((height < -vscreen_bounds) && (y + height < -vscreen_bounds)) + height = -vscreen_bounds; + else if (height + y > view.getVisibleHeight() + vscreen_bounds) + height = view.getVisibleHeight() + vscreen_bounds - y; + context.fillRectangle(x, y, width, height); + + } + + @Override + public void fillGradientRectangle(int x, int y, int width, int height, boolean vertical) { + x = Math.round(x * view.zoomValue); + y = Math.round(y * view.zoomValue); + width = Math.round(width * view.zoomValue); + height = Math.round(height * view.zoomValue); + IColor tempColor = foreGround; + setForeground(gradientColor); + x = view.contentsToViewX(x); + y = view.contentsToViewY(y); + + if (x < -vscreen_bounds) { + width = width + x + vscreen_bounds; + x = -vscreen_bounds; + } + if (y < -vscreen_bounds) { + height = height + y + vscreen_bounds; + y = -vscreen_bounds; + } + if ((width < -vscreen_bounds) && (x + width < -vscreen_bounds)) + width = -vscreen_bounds; + else if (width + x > view.getVisibleWidth() + vscreen_bounds) + width = view.getVisibleWidth() + vscreen_bounds - x; + if ((height < -vscreen_bounds) && (y + height < -vscreen_bounds)) + height = -vscreen_bounds; + else if (height + y > view.getVisibleHeight() + vscreen_bounds) + height = view.getVisibleHeight() + vscreen_bounds - y; + if (vertical) { + context.fillGradientRectangle(x, y, width, height, vertical); + } + else { + context.fillGradientRectangle(x + width, y, -width, height + 1, vertical); + } + setForeground(tempColor); + } + + + @Override + public int textExtent(String name) { + return ((Point) (context.textExtent(name))).x; + } + + @Override + public void drawText(String string, int x, int y, boolean trans) { + x = Math.round(x * view.zoomValue); + y = Math.round(y * view.zoomValue); + context.drawText(string, view.contentsToViewX(x), view.contentsToViewY(y), trans); + if (drawWithFocus) { + Point r = context.textExtent(string); + context.drawFocus(x - 1, y - 1, r.x + 2, r.y + 2); + } + } + + @Override + public void drawText(String string, int x, int y) { + x = Math.round(x * view.zoomValue); + y = Math.round(y * view.zoomValue); + context.drawText(string, view.contentsToViewX(x), view.contentsToViewY(y), true); + if (drawWithFocus) { + Point r = context.textExtent(string); + context.drawFocus(x - 1, y - 1, r.x + 2, r.y + 2); + } + } + + @Override + public void fillOval(int x, int y, int width, int height) { + x = Math.round(x * view.zoomValue); + y = Math.round(y * view.zoomValue); + width = Math.round(width * view.zoomValue); + height = Math.round(height * view.zoomValue); + context.fillOval(view.contentsToViewX(x), view.contentsToViewY(y), width, height); + } + + @Override + public IColor getBackground() { + if (backGround != null) + if (backGround.getColor() instanceof Color) + if (!((Color) (backGround.getColor())).isDisposed()) + return backGround; + return ColorImpl.getSystemColor(SWT.COLOR_WHITE); + } + + @Override + public IColor getForeground() { + if (foreGround != null) + if (foreGround.getColor() instanceof Color) + if (!((Color) (foreGround.getColor())).isDisposed()) + return foreGround; + return ColorImpl.getSystemColor(SWT.COLOR_WHITE); + } + + @Override + public void setBackground(IColor color) { + if (color == null) + return; + if (color.getColor() instanceof Color) { + context.setBackground((Color) color.getColor()); + backGround = color; + } + } + + @Override + public void setForeground(IColor color) { + if (color == null) + return; + if (color.getColor() instanceof Color) { + Color c = (Color) color.getColor(); + if (!c.isDisposed()) { + context.setForeground(c); + foreGround = color; + } + } + } + + @Override + public void setGradientColor(IColor color) { + if (color == null) + return; + if (color.getColor() instanceof Color) + gradientColor = color; + } + + @Override + public void setLineWidth(int width) { + if (view.isPrinting()) + context.setLineWidth(width * 2); + else + context.setLineWidth(width); + } + + @Override + public int getLineWidth() { + return context.getLineWidth(); + } + + // Linux GTK Workaround + protected void localDrawText(String string, int x, int y, boolean trans) { + Point r = context.textExtent(string); + if (!trans) + context.fillRectangle(x, y, r.x, r.y); + context.drawText(string, x, y, trans); + if ((drawWithFocus) && (string.length() > 1)) + context.drawFocus(x - 1, y - 1, r.x + 2, r.y + 2); + } + + @Override + public void drawTextTruncatedCentred(String name, int _x, int _y, int width, int height, boolean trans) { + Point tx = context.textExtent(name); + _x = Math.round(_x * view.zoomValue); + int y = 0; + // Workaround to avoid round problems for some special cases (not very nice) + if (_y != getContentsY()) { + _y = Math.round(_y * view.zoomValue); + y = view.contentsToViewY(_y); + } + width = Math.round(width * view.zoomValue); + height = Math.round(height * view.zoomValue); + int x = view.contentsToViewX(_x); + if (tx.y > height) + return; + + // Adjust height and y + if (y < -vscreen_bounds) { + height = height + y + vscreen_bounds; + y = -vscreen_bounds; + } + if ((height < -vscreen_bounds) && (y + height < -vscreen_bounds)) + height = -vscreen_bounds; + else if (height + y > view.getVisibleHeight() + vscreen_bounds) + height = view.getVisibleHeight() + vscreen_bounds - y; + + if (tx.x <= width) { + localDrawText(name, x + 1 + (width - tx.x) / 2, y + 1 + (height - tx.y) / 2, trans); + } else { + String nameToDisplay = name; + for (int i = name.length() - 1; i >= 0 && context.textExtent(nameToDisplay).x >= width; i--) + nameToDisplay = name.substring(0, i); + int dotCount = 0; + for (int i = 1; i <= 3 && nameToDisplay.length() - i > 0; i++) + dotCount++; + nameToDisplay = nameToDisplay.substring(0, nameToDisplay.length() - dotCount); + StringBuffer buf = new StringBuffer(nameToDisplay); + for (int i = 0; i < dotCount; i++) { + buf.append("."); //$NON-NLS-1$ + // nameToDisplay = nameToDisplay + "."; //$NON-NLS-1$ + } + nameToDisplay = buf.toString(); + localDrawText(nameToDisplay, x + 1 + (width - context.textExtent(nameToDisplay).x) / 2, y + 1 + (height - context.textExtent(nameToDisplay).y) / 2, trans); + } + } + + @Override + public void drawTextTruncated(String name, int _x, int _y, int width, int height, boolean trans) { + _x = Math.round(_x * view.zoomValue); + _y = Math.round(_y * view.zoomValue); + width = Math.round(width * view.zoomValue); + height = Math.round(height * view.zoomValue); + int x = view.contentsToViewX(_x); + int y = view.contentsToViewY(_y); + if (context.textExtent(name).x <= width) { + localDrawText(name, x + 1, y + 1 + height, trans); + } else { + String nameToDisplay = name; + for (int i = name.length() - 1; i >= 0 && context.textExtent(nameToDisplay).x >= width; i--) + nameToDisplay = name.substring(0, i); + int dotCount = 0; + for (int i = 1; i <= 3 && nameToDisplay.length() - i > 0; i++) + dotCount++; + nameToDisplay = nameToDisplay.substring(0, nameToDisplay.length() - dotCount); + + StringBuffer buf = new StringBuffer(nameToDisplay); + + for (int i = 0; i < dotCount; i++) { + buf.append("."); //$NON-NLS-1$ + // nameToDisplay = nameToDisplay + "."; //$NON-NLS-1$ + } + nameToDisplay = buf.toString(); + localDrawText(nameToDisplay, x + 1, y + 1 + height, trans); + } + } + + @Override + public void drawImage(IImage image, int _x, int _y, int maxWith, int maxHeight) { + Image img = null; + if (image != null && image.getImage() instanceof Image) + img = (Image) image.getImage(); + else { + _x = Math.round(_x * view.zoomValue); + _y = Math.round(_y * view.zoomValue); + int x = view.contentsToViewX(_x); + int y = view.contentsToViewY(_y); + float tempZoom = view.zoomValue; + int width = Math.round(maxWith * tempZoom); + int height = Math.round(maxHeight * tempZoom); + context.setBackground(view.getDisplay().getSystemColor(SWT.COLOR_RED)); + context.fillRectangle(x, y, width, height); + return; + } + _x = Math.round(_x * view.zoomValue); + _y = Math.round(_y * view.zoomValue); + int x = view.contentsToViewX(_x); + int y = view.contentsToViewY(_y); + Rectangle b = ((Image) image.getImage()).getBounds(); + int width = b.width; + int height = b.height; + if (width > maxWith) + width = maxWith; + if (height > maxHeight) + height = maxHeight; + float tempZoom = view.zoomValue; + width = Math.round(width * tempZoom); + height = Math.round(height * tempZoom); + + if (view.printing && width > 0 && height > 0) { + Image dbuffer = new Image(view.getDisplay(), width, height); + GC tempgc = new GC(dbuffer); + tempgc.drawImage(img, 0, 0, b.width, b.height, 0, 0, width, height); + Image dbuffer2 = new Image(view.getDisplay(), dbuffer.getImageData()); + context.drawImage(dbuffer2, x, y); + tempgc.dispose(); + dbuffer.dispose(); + dbuffer2.dispose(); + } else { + context.drawImage(img, 0, 0, b.width, b.height, x, y, width, height); + } + } + + @Override + public void drawArc(int x, int y, int width, int height, int startAngle, int endAngle) { + x = Math.round(x * view.zoomValue); + y = Math.round(y * view.zoomValue); + width = Math.round(width * view.zoomValue); + height = Math.round(height * view.zoomValue); + if (width == 0 || height == 0 || endAngle == 0) + return; + context.drawArc(view.contentsToViewX(x), view.contentsToViewY(y), width, height, startAngle, endAngle); + } + + @Override + public void setFont(IFont font) { + if (font.getFont() != null && ((Font) font.getFont()).getFontData().length > 0) { + FontData fontData = ((Font) font.getFont()).getFontData()[0]; + if (SDViewPref.getInstance().fontLinked() || view.printing) { + int h = Math.round(fontData.getHeight() * view.zoomValue); + if (h > 0) + fontData.setHeight(h); + } + if (tempFont != null) + tempFont.dispose(); + tempFont = new Font(Display.getCurrent(), fontData); + context.setFont(tempFont); + } + } + + @Override + public int getFontHeight(IFont font) { + if (font.getFont() != null && (font.getFont() instanceof Font) && ((Font) font.getFont()).getFontData().length > 0) { + Font toRestore = context.getFont(); + context.setFont((Font) font.getFont()); + int height = context.textExtent("lp").y;//$NON-NLS-1$ + context.setFont(toRestore); + return height; + } + return 0; + } + + protected int getCurrentFontHeight() { + return context.textExtent("lp").y; //$NON-NLS-1$ + } + + @Override + public int getFontWidth(IFont font) { + if ((font.getFont() != null) && (font.getFont() instanceof Font)) { + Font toRestore = context.getFont(); + context.setFont((Font) font.getFont()); + int width = context.getFontMetrics().getAverageCharWidth(); + context.setFont(toRestore); + return width; + } + return 0; + } + + public void dispose() { + if (tempFont != null) + tempFont.dispose(); + tempFont = null; + if (context != null) + context.dispose(); + context = null; + } + + @Override + public float getZoom() { + if (view != null) + return view.zoomValue; + else + return 1; + } + + @Override + public int getLineDotStyle() { + return SWT.LINE_DOT; + } + + @Override + public int getLineDashStyle() { + return SWT.LINE_DASH; + } + + @Override + public int getLineSolidStyle() { + return SWT.LINE_SOLID; + } + + @Override + public IColor createColor(int r, int g, int b) { + return new ColorImpl(Display.getDefault(), r, g, b); + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC#setDrawTextWithFocusStyle(boolean) + */ + @Override + public void setDrawTextWithFocusStyle(boolean focus) { + drawWithFocus = focus; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/SDView.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/SDView.java new file mode 100755 index 0000000000..54787eebc6 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/SDView.java @@ -0,0 +1,1003 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: SDView.java,v 1.2 2008/01/24 02:29:01 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd; + +import java.util.Iterator; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IContributionItem; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.linuxtools.tmf.ui.ITmfImageConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.BaseMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Frame; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SyncMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SyncMessageReturn; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ConfigureMinMax; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.FirstPage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.KeyBindingsManager; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.LastPage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.MoveToMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.NextPage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.OpenSDFiltersDialog; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.OpenSDFindDialog; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.OpenSDPagesDialog; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.PrevPage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.Print; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ShowNodeEnd; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ShowNodeStart; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.Zoom; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.Zoom.ZoomType; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.IExtendedFilterProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.IExtendedFindProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.IPropertySheetPage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDAdvancedPagingProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDCollapseProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDExtendedActionBarProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDFilterProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDFindProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDGraphNodeSupporter; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDPagingProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDPropertiesProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.IUml2SDLoader; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.LoadersManager; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IViewReference; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.actions.ActionFactory; +import org.eclipse.ui.part.ViewPart; + +/** + * @author sveyrier + * + */ +public class SDView extends ViewPart { + + protected SDWidget sdWidget = null; + protected TimeCompressionBar timeCompressionBar = null; + protected ISDFindProvider sdFindProvider = null; + protected ISDPagingProvider sdPagingProvider = null; + protected ISDFilterProvider sdFilterProvider = null; + protected IExtendedFilterProvider sdExFilterProvider = null; + protected IExtendedFindProvider sdExFindProvider = null; + protected ISDExtendedActionBarProvider sdExtendedActionBarProvider = null; + protected ISDPropertiesProvider sdPropertiesProvider = null; + + protected NextPage nextPageButton = null; + protected PrevPage prevPageButton = null; + protected FirstPage firstPageButton = null; + protected LastPage lastPageButton = null; + + protected MenuManager menuMgr = null; + + protected boolean needInit = true; + + /* + * (non-Javadoc) + * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite) + */ + @Override + public void createPartControl(Composite c) { + Composite parent = new Composite(c, SWT.NONE); + GridLayout parentLayout = new GridLayout(); + parentLayout.numColumns = 2; + parentLayout.marginWidth = 0; + parentLayout.marginHeight = 0; + parent.setLayout(parentLayout); + + GridData timeLayoutdata = new GridData(GridData.GRAB_VERTICAL | GridData.VERTICAL_ALIGN_FILL); + timeLayoutdata.widthHint = 10; + GridData seqDiagLayoutData = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL | GridData.VERTICAL_ALIGN_FILL); + timeCompressionBar = new TimeCompressionBar(parent, SWT.NONE); + timeCompressionBar.setLayoutData(timeLayoutdata); + sdWidget = new SDWidget(parent, SWT.NONE);// SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + sdWidget.setLayoutData(seqDiagLayoutData); + sdWidget.setSite(this); + sdWidget.setTimeBar(timeCompressionBar); + + // Add this view to the key bindings manager + KeyBindingsManager.getInstance().add(this.getSite().getId()); + + createCoolbarContent(); + + hookContextMenu(); + + timeCompressionBar.setVisible(false); + parent.layout(true); + + Print print = new Print(this); + getViewSite().getActionBars().setGlobalActionHandler(ActionFactory.PRINT.getId(), print); + + needInit = restoreLoader(); + } + + /** + * Load a blank page that is supposed to explain that a kind of interaction must be chosen + */ + protected void loadBlank() { + IUml2SDLoader l = new IUml2SDLoader() { + /* + * (non-Javadoc) + * @see + * org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.IUml2SDLoader#setViewer(org.eclipse.linuxtools.tmf.ui + * .views.uml2sd.SDView) + */ + @Override + public void setViewer(SDView viewer) { + // Nothing to do + Frame f = new Frame(); + f.setName(""); //$NON-NLS-1$ + viewer.setFrame(f); + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.IUml2SDLoader#getTitleString() + */ + @Override + public String getTitleString() { + return ""; //$NON-NLS-1$ + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.IUml2SDLoader#aboutToBeReplaced() + */ + @Override + public void dispose() { + } + }; + l.setViewer(this); + setContentDescription(l.getTitleString()); + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.part.WorkbenchPart#setFocus() + */ + @Override + public void setFocus() { + if (sdWidget != null) { + // update actions for key bindings + KeyBindingsManager.getInstance().setSdView(this); + sdWidget.setFocus(); + } + if (isViewReady() && needInit) { + needInit = restoreLoader(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.part.WorkbenchPart#dispose() + */ + @Override + public void dispose() { + KeyBindingsManager.getInstance().remove(this.getSite().getId()); + super.dispose(); + } + + /** + * @return The SD widget. + */ + public SDWidget getSDWidget() { + return sdWidget; + } + + /** + * Set the find provider for the opened sequence diagram viewer
+ * If the provider is not set, the find menu item will not be available in the viewer
+ * A find provider is called back when the user perform a find action
+ * The find provider is responsible to move the sequence diagram to the GraphNode which match the + * find criteria as well as to highlight the GraphNode + * + * @param provider the search provider + */ + public void setSDFindProvider(ISDFindProvider provider) { + sdFindProvider = provider; + sdExFindProvider = null; + createCoolbarContent(); + if (provider != null) { + KeyBindingsManager.getInstance().setFindEnabled(true); + } + else { + KeyBindingsManager.getInstance().setFindEnabled(false); + } + } + + /** + * Set the find provider for the opened sequence diagram viewer
+ * If the provider is not set, the find menu item will not be available in the viewer
+ * A find provider is called back when the user perform a find action
+ * If the extended find provider is set, it replaces the regular find provider (sdFindProvider).
+ * @param provider + */ + public void setExtendedFindProvider(IExtendedFindProvider provider) { + sdExFindProvider = provider; + sdFindProvider = null; + createCoolbarContent(); + if (provider != null) { + KeyBindingsManager.getInstance().setFindEnabled(true); + } + else { + KeyBindingsManager.getInstance().setFindEnabled(false); + } + } + + /** + * Returns the extended find provider + * + * @return extended find provider. + */ + public IExtendedFindProvider getExtendedFindProvider() { + return sdExFindProvider; + } + + /** + * Resets all providers. + */ + public void resetProviders() { + KeyBindingsManager.getInstance().setFindEnabled(false); + sdFindProvider = null; + sdExFindProvider = null; + sdFilterProvider = null; + sdExFilterProvider = null; + sdPagingProvider = null; + sdExtendedActionBarProvider = null; + sdPropertiesProvider = null; + if ((sdWidget != null) && (!sdWidget.isDisposed())) { + sdWidget.setCollapseProvider(null); + } + } + + /** + * Set the filter provider for the opened sequence diagram viewer
+ * If the provider is not set, the filter menu item will not be available in the viewer
+ * A filter provider is called back when the user perform a filter action
+ * + * @param provider the filter provider + */ + public void setSDFilterProvider(ISDFilterProvider provider) { + sdFilterProvider = provider; + // Both systems can be used now, commenting out next statement + // sdExFilterProvider = null; + createCoolbarContent(); + } + + /** + * Sets the extended filter provider for the opend sequence diagram viewer. + * @param provider + */ + public void setExtendedFilterProvider(IExtendedFilterProvider provider) { + sdExFilterProvider = provider; + // Both systems can be used now, commenting out next statement + // sdFilterProvider = null; + createCoolbarContent(); + } + + /** + * Returns the extended find provider. + * + * @return The extended find provider. + */ + public IExtendedFilterProvider getExtendedFilterProvider() { + return sdExFilterProvider; + } + + /** + * Register the given provider to support Drag and Drop collapsing. This provider is + * responsible of updating the Frame. + * + * @param provider - the provider to register + */ + public void setCollapsingProvider(ISDCollapseProvider provider) { + if ((sdWidget != null) && (!sdWidget.isDisposed())) { + sdWidget.setCollapseProvider(provider); + } + } + + /** + * Set the page provider for the opened sequence diagram viewer
+ * If the sequence diagram provided (see setFrame) need to be split in many parts, a paging provider must be + * provided in order to handle page change requested by the user
+ * Set a page provider will create the next and previous page buttons in the viewer coolBar + * + * @param provider the paging provider + */ + public void setSDPagingProvider(ISDPagingProvider provider) { + sdPagingProvider = provider; + createCoolbarContent(); + } + + /** + * Returns the current page provider for the view + * + * @return the paging provider + */ + public ISDPagingProvider getSDPagingProvider() { + return sdPagingProvider; + } + + /** + * Returns the current find provider for the view + * + * @return the find provider + */ + public ISDFindProvider getSDFindProvider() { + return sdFindProvider; + } + + /** + * Returns the current filter provider for the view + * + * @return the filter provider + */ + public ISDFilterProvider getSDFilterProvider() { + return sdFilterProvider; + } + + /** + * Set the extended action bar provider for the opened sequence diagram viewer
+ * This allow to add programmatically actions in the coolbar and/or in the drop-down menu + * + * @param provider the search provider + */ + public void setSDExtendedActionBarProvider(ISDExtendedActionBarProvider provider) { + sdExtendedActionBarProvider = provider; + createCoolbarContent(); + } + + /** + * Returns the current extended action bar provider for the view + * + * @return the extended action bar provider + */ + public ISDExtendedActionBarProvider getSDExtendedActionBarProvider() { + return sdExtendedActionBarProvider; + } + + /** + * Set the properties view provider for the opened sequence diagram viewer
+ * + * @param provider the properties provider + */ + public void setSDPropertiesProvider(ISDPropertiesProvider provider) { + sdPropertiesProvider = provider; + } + + /** + * Returns the current extended action bar provider for the view + * + * @return the extended action bar provider + */ + public ISDPropertiesProvider getSDPropertiesProvider() { + return sdPropertiesProvider; + } + + /** + * Creates the basic sequence diagram menu + */ + protected void hookContextMenu() { + menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$ + menuMgr.setRemoveAllWhenShown(true); + menuMgr.addMenuListener(new IMenuListener() { + @Override + public void menuAboutToShow(IMenuManager manager) { + fillContextMenu(manager); + } + }); + Menu menu = menuMgr.createContextMenu(sdWidget.getViewControl()); + sdWidget.getViewControl().setMenu(menu); + getSite().registerContextMenu(menuMgr, sdWidget.getSelectionProvider()); + } + + /** + * Returns the context menu manager + * + * @return the menu manager + */ + public MenuManager getMenuManager() { + return menuMgr; + } + + /** + * Fills the basic sequence diagram menu and define the dynamic menu item insertion point + * + * @param manager the menu manager + */ + protected void fillContextMenu(IMenuManager manager) { + manager.add(new Separator("Additions")); //$NON-NLS-1$ + if (getSDWidget() != null && getSDWidget().currentGraphNode != null) { + ISelectionProvider selProvider = sdWidget.getSelectionProvider(); + ISelection sel = selProvider.getSelection(); + int nbMessage = 0; + Iterator it = ((StructuredSelection) sel).iterator(); + while (it.hasNext()) { + Object node = it.next(); + if (node instanceof BaseMessage) { + nbMessage++; + } + } + if (nbMessage != 1) { + return; + } + GraphNode node = getSDWidget().currentGraphNode; + if (node instanceof SyncMessageReturn) { + if (((SyncMessageReturn) node).getMessage() != null) { + Action goToMessage = new MoveToMessage(this); + goToMessage.setText(SDMessages._39); + manager.add(goToMessage); + } + } + if (node instanceof SyncMessage) { + if (((SyncMessage) node).getMessageReturn() != null) { + Action goToMessage = new MoveToMessage(this); + goToMessage.setText(SDMessages._40); + manager.add(goToMessage); + } + } + } + manager.add(new Separator("MultiSelectAdditions")); //$NON-NLS-1$ + } + + /** + * Enables/Disables an action with given name. + * + * @param actionName The action name + * @param state true or false + */ + public void setEnableAction(String actionName, boolean state) { + IActionBars bar = getViewSite().getActionBars(); + if (bar != null) { + IContributionItem item = bar.getMenuManager().find(actionName); + if ((item != null) && (item instanceof ActionContributionItem)) { + IAction action = ((ActionContributionItem) item).getAction(); + if (action != null) { + action.setEnabled(state); + } + item.setVisible(state); + bar.updateActionBars(); + } + } + } + + /** + * Creates the coolBar icon depending on the actions supported by the Sequence Diagram provider
+ * - Navigation buttons are displayed if ISDPovider.HasPaging return true
+ * - Navigation buttons are enabled depending on the value return by ISDPovider.HasNext and HasPrev
+ * + * @see ISDGraphNodeSupporter Action support definition + * @see SDView#setSDFilterProvider(ISDFilterProvider) + * @see SDView#setSDFindProvider(ISDFindProvider) + * @see SDView#setSDPagingProvider(ISDPagingProvider) + */ + protected void createCoolbarContent() { + IActionBars bar = getViewSite().getActionBars(); + + bar.getMenuManager().removeAll(); + bar.getToolBarManager().removeAll(); + + createMenuGroup(); + + Zoom resetZoom = new Zoom(this, ZoomType.ZOOM_RESET); + bar.getMenuManager().appendToGroup("UML2SD_OTHER_COMMANDS", resetZoom);//$NON-NLS-1$ + bar.getToolBarManager().appendToGroup("UML2SD_OTHER_COMMANDS", resetZoom); //$NON-NLS-1$ + + Zoom noZoom = new Zoom(this, ZoomType.ZOOM_NONE); + noZoom.setChecked(true); + bar.getMenuManager().appendToGroup("UML2SD_OTHER_COMMANDS", noZoom);//$NON-NLS-1$ + bar.getToolBarManager().appendToGroup("UML2SD_OTHER_COMMANDS", noZoom); //$NON-NLS-1$ + + Zoom zoomIn = new Zoom(this, ZoomType.ZOOM_IN); + bar.getMenuManager().appendToGroup("UML2SD_OTHER_COMMANDS", zoomIn);//$NON-NLS-1$ + bar.getToolBarManager().appendToGroup("UML2SD_OTHER_COMMANDS", zoomIn); //$NON-NLS-1$ + + Zoom zoomOut = new Zoom(this, ZoomType.ZOOM_OUT); + bar.getMenuManager().appendToGroup("UML2SD_OTHER_COMMANDS", zoomOut);//$NON-NLS-1$ + bar.getToolBarManager().appendToGroup("UML2SD_OTHER_COMMANDS", zoomOut); //$NON-NLS-1$ + + MenuManager navigation = new MenuManager(SDMessages._77); + + ShowNodeStart showNodeStart = new ShowNodeStart(this); + showNodeStart.setText(SDMessages.uml_25); + + showNodeStart.setId("org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ShowNodeStart");//$NON-NLS-1$ + showNodeStart.setActionDefinitionId("org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ShowNodeStart");//$NON-NLS-1$ + navigation.add(showNodeStart); + + ShowNodeEnd showNodeEnd = new ShowNodeEnd(this); + showNodeEnd.setText(SDMessages.uml_23); + + showNodeEnd.setId("org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ShowNodeEnd");//$NON-NLS-1$ + showNodeEnd.setActionDefinitionId("org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ShowNodeEnd");//$NON-NLS-1$ + navigation.add(showNodeEnd); + + bar.getMenuManager().appendToGroup("UML2SD_OTHER_COMMANDS", navigation); //$NON-NLS-1$ + + ConfigureMinMax minMax = new ConfigureMinMax(this); + minMax.setText(SDMessages.uml_45); + minMax.setId("org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ConfigureMinMax");//$NON-NLS-1$ + bar.getMenuManager().appendToGroup("UML2SD_OTHER_COMMANDS", minMax); //$NON-NLS-1$ + + if ((sdWidget.getFrame() != null) && (sdWidget.getFrame().hasTimeInfo())) + minMax.setEnabled(true); + else + minMax.setEnabled(false); + + // Do we need to display a paging item + if (sdPagingProvider != null) { + nextPageButton = new NextPage(this); + bar.getToolBarManager().appendToGroup("UML2SD_OTHER_COMMANDS", nextPageButton); //$NON-NLS-1$ + nextPageButton.setEnabled(sdPagingProvider.hasNextPage()); + bar.getMenuManager().appendToGroup("UML2SD_OTHER_COMMANDS", nextPageButton); //$NON-NLS-1$ + + prevPageButton = new PrevPage(this); + bar.getToolBarManager().appendToGroup("UML2SD_OTHER_COMMANDS", prevPageButton); //$NON-NLS-1$ + prevPageButton.setEnabled(sdPagingProvider.hasPrevPage()); + bar.getMenuManager().appendToGroup("UML2SD_OTHER_COMMANDS", prevPageButton); //$NON-NLS-1$ + + firstPageButton = new FirstPage(this); + bar.getToolBarManager().appendToGroup("UML2SD_OTHER_COMMANDS", firstPageButton); //$NON-NLS-1$ + firstPageButton.setEnabled(sdPagingProvider.hasPrevPage()); + bar.getMenuManager().appendToGroup("UML2SD_OTHER_COMMANDS", firstPageButton); //$NON-NLS-1$ + + lastPageButton = new LastPage(this); + bar.getToolBarManager().appendToGroup("UML2SD_OTHER_COMMANDS", lastPageButton); //$NON-NLS-1$ + lastPageButton.setEnabled(sdPagingProvider.hasNextPage()); + bar.getMenuManager().appendToGroup("UML2SD_OTHER_COMMANDS", lastPageButton); //$NON-NLS-1$ + } + + if (sdExFilterProvider != null) { + Action action = sdExFilterProvider.getFilterAction(); + if (action != null) { + if (action.getId() == null) + action.setId("org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.extendedFilter"); //$NON-NLS-1$ + if (action.getImageDescriptor() == null) + action.setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_FILTERS)); + if (action.getText() == null || action.getText().length() == 0) + action.setText(SDMessages._42); + bar.getMenuManager().prependToGroup("UML2SD_FILTERING", action); //$NON-NLS-1$ + bar.getToolBarManager().prependToGroup("UML2SD_FILTERING", action); //$NON-NLS-1$ + } + } + // Both systems can be used now: commenting out else keyword + /* else */if (sdFilterProvider != null) { + bar.getMenuManager().appendToGroup("UML2SD_FILTERING", new OpenSDFiltersDialog(this, sdFilterProvider)); //$NON-NLS-1$ + // No longer in the coolbar: commenting out next statement + //bar.getToolBarManager().appendToGroup("UML2SD_FILTERING",new OpenSDFiltersDialog(this, sdFilterProvider)); //$NON-NLS-1$ + } + if (sdPagingProvider != null && sdPagingProvider instanceof ISDAdvancedPagingProvider) { + IContributionItem sdPaging = bar.getMenuManager().find(OpenSDPagesDialog.ID); + if (sdPaging != null) { + bar.getMenuManager().remove(sdPaging); + sdPaging = null; + } + bar.getMenuManager().appendToGroup("UML2SD_OTHER_COMMANDS", new OpenSDPagesDialog(this, (ISDAdvancedPagingProvider) sdPagingProvider)); //$NON-NLS-1$ + updatePagesMenuItem(bar); + } + + if (sdExFindProvider != null) { + Action action = sdExFindProvider.getFindAction(); + if (action != null) { + if (action.getId() == null) + action.setId("org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.extendedFind"); //$NON-NLS-1$ + if (action.getImageDescriptor() == null) + action.setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_SEARCH_SEQ)); + if (action.getText() == null) + action.setText(SDMessages._41); + bar.getMenuManager().appendToGroup("UML2SD_OTHER_COMMANDS", action); //$NON-NLS-1$ + bar.getToolBarManager().appendToGroup("UML2SD_OTHER_COMMANDS", action); //$NON-NLS-1$ + } + } else if (sdFindProvider != null) { + bar.getMenuManager().appendToGroup("UML2SD_OTHER_COMMANDS", new OpenSDFindDialog(this)); //$NON-NLS-1$ + bar.getToolBarManager().appendToGroup("UML2SD_OTHER_COMMANDS", new OpenSDFindDialog(this)); //$NON-NLS-1$ + } + + if (sdExtendedActionBarProvider != null) { + sdExtendedActionBarProvider.supplementCoolbarContent(bar); + } + + bar.updateActionBars(); + } + + /** + * Updates the view coolbar buttons state according to the value return by: - + * ISDExtendedActionBarProvider.hasNextPage()
+ * - ISDExtendedActionBarProvider.hasPrevPage()
+ * + */ + public void updateCoolBar() { + if (sdPagingProvider != null) { + IActionBars bar = getViewSite().getActionBars(); + if (bar == null) + return; + IToolBarManager barManager = bar.getToolBarManager(); + if (barManager == null) + return; + IContributionItem nextPage = barManager.find(NextPage.ID); + if (nextPage != null && nextPage instanceof ActionContributionItem) { + IAction nextPageAction = ((ActionContributionItem) nextPage).getAction(); + if (nextPageAction != null && nextPageAction instanceof NextPage) { + ((NextPage) nextPageAction).setEnabled(sdPagingProvider.hasNextPage()); + } + } + + IContributionItem prevPage = barManager.find(PrevPage.ID); + if (prevPage != null && prevPage instanceof ActionContributionItem) { + IAction prevPageAction = ((ActionContributionItem) prevPage).getAction(); + if (prevPageAction != null && prevPageAction instanceof PrevPage) { + ((PrevPage) prevPageAction).setEnabled(sdPagingProvider.hasPrevPage()); + } + } + + IContributionItem firstPage = barManager.find(FirstPage.ID); + if (firstPage != null && firstPage instanceof ActionContributionItem) { + IAction firstPageAction = ((ActionContributionItem) firstPage).getAction(); + if (firstPageAction != null && firstPageAction instanceof FirstPage) { + ((FirstPage) firstPageAction).setEnabled(sdPagingProvider.hasPrevPage()); + } + } + + IContributionItem lastPage = barManager.find(LastPage.ID); + if (lastPage != null && lastPage instanceof ActionContributionItem) { + IAction lastPageAction = ((ActionContributionItem) lastPage).getAction(); + if (lastPageAction != null && lastPageAction instanceof LastPage) { + ((LastPage) lastPageAction).setEnabled(sdPagingProvider.hasNextPage()); + } + } + + updatePagesMenuItem(bar); + } + } + + /** + * Enables or disables the Pages... menu item, depending on the number of pages + * + * @param bar the bar containing the action + */ + protected void updatePagesMenuItem(IActionBars bar) { + if (sdPagingProvider instanceof ISDAdvancedPagingProvider) { + IMenuManager menuManager = bar.getMenuManager(); + ActionContributionItem contributionItem = (ActionContributionItem) menuManager.find(OpenSDPagesDialog.ID); + IAction openSDPagesDialog = null; + if (contributionItem != null) { + openSDPagesDialog = contributionItem.getAction(); + } + + if (openSDPagesDialog != null && openSDPagesDialog instanceof OpenSDPagesDialog) { + openSDPagesDialog.setEnabled(((ISDAdvancedPagingProvider) sdPagingProvider).pagesCount() > 1); + } + } + } + + /** + * The frame to render (the sequence diagram) + * + * @param frame the frame to display + */ + public void setFrame(Frame frame) { + setFrame(frame, true); + } + + /** + * The frame to render (the sequence diagram) + * + * @param frame the frame to display + */ + protected void setFrame(Frame frame, boolean resetPosition) { + if (sdWidget == null) + return; + + if (frame == null) { + loadBlank(); + return; + } + + IUml2SDLoader l = LoadersManager.getInstance().getCurrentLoader(getViewSite().getId(), this); + + if ((l != null) && (l.getTitleString() != null)) { + setContentDescription(l.getTitleString()); + } + + if (getSDWidget() != null) + getSDWidget().setFrame(frame, resetPosition); + + if (timeCompressionBar != null) + timeCompressionBar.setFrame(frame); + updateCoolBar(); + if (!frame.hasTimeInfo()) { + Composite parent = timeCompressionBar.getParent(); + timeCompressionBar.setVisible(false); + parent.layout(true); + } else { + Composite parent = timeCompressionBar.getParent(); + timeCompressionBar.setVisible(true); + parent.layout(true); + } + IContributionItem shortKeysMenu = getViewSite().getActionBars().getMenuManager().find("org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers");//$NON-NLS-1$ + MenuManager shortKeys = (MenuManager) shortKeysMenu; + if (shortKeys != null) { + IContributionItem[] items = shortKeys.getItems(); + for (int i = 0; i < items.length; i++) { + if (items[i] instanceof ActionContributionItem) { + IAction action = ((ActionContributionItem) items[i]).getAction(); + if (action != null) + action.setEnabled(true); + } + } + } + createCoolbarContent(); + } + + /** + * Activate or deactivate the short key command given in parameter (see plugin.xml) + * + * @param id the command id defined in the plugin.xml + * @param value the state value + */ + public void setEnableCommand(String id, boolean value) { + IContributionItem shortKeysMenu = getViewSite().getActionBars().getMenuManager().find("org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers");//$NON-NLS-1$ + MenuManager shortKeys = (MenuManager) shortKeysMenu; + if (shortKeys == null) + return; + IContributionItem item = shortKeys.find(id); + if ((item != null) && (item instanceof ActionContributionItem)) { + IAction action = ((ActionContributionItem) item).getAction(); + if (action != null) + action.setEnabled(value); + } + } + + /** + * Set the frame from an other thread than the one executing the main loop + * + * @param frame + */ + public void setFrameSync(final Frame frame) { + if (getSDWidget() == null || getSDWidget().isDisposed()) { + return; + } + getSDWidget().getDisplay().syncExec(new Runnable() { + @Override + public void run() { + if (getSDWidget() == null || getSDWidget().isDisposed()) { + return; + } + setFrame(frame); + } + }); + + } + + /** + * Ensure an object is visible from an other thread than the one executing the main loop + * + * @param sm + */ + public void ensureVisibleSync(final GraphNode sm) { + getSDWidget().getDisplay().syncExec(new Runnable() { + @Override + public void run() { + if (getSDWidget() == null || getSDWidget().isDisposed()) { + return; + } + getSDWidget().ensureVisible(sm); + } + }); + } + + /** + * Set the frame and ensure an object is visible from an other thread than the one executing the main loop + * + * @param sm + */ + public void setFrameAndEnsureVisibleSync(final Frame frame, final GraphNode sm) { + if (getSDWidget() == null || getSDWidget().isDisposed()) { + return; + } + getSDWidget().getDisplay().syncExec(new Runnable() { + @Override + public void run() { + if (getSDWidget() == null || getSDWidget().isDisposed()) { + return; + } + setFrameAndEnsureVisible(frame, sm); + } + }); + } + + /** + * Set the frame and ensure an object is visible + * + * @param sm + */ + public void setFrameAndEnsureVisible(Frame frame, GraphNode sm) { + getSDWidget().clearSelection(); + setFrame(frame, false); + getSDWidget().ensureVisible(sm); + } + + /** + * Set the frame and ensure an object is visible from an other thread than the one executing the main loop + * + * @param sm + */ + public void setFrameAndEnsureVisibleSync(final Frame frame, final int x, final int y) { + if (getSDWidget() == null || getSDWidget().isDisposed()) { + return; + } + + getSDWidget().getDisplay().syncExec(new Runnable() { + @Override + public void run() { + setFrameAndEnsureVisible(frame, x, y); + } + }); + } + + /** + * Set the frame and ensure an object is visible + * + * @param sm + */ + public void setFrameAndEnsureVisible(Frame frame, int x, int y) { + getSDWidget().clearSelection(); + setFrame(frame, false); + getSDWidget().ensureVisible(x, y); + getSDWidget().redraw(); + } + + /** + * waitCursor is the cursor to be displayed when long tasks are running + */ + protected Cursor waitCursor; + + /** + * Toggle between default and wait cursors from an other thread than the one executing the main loop + */ + public void toggleWaitCursorAsync(final boolean wait_) { + if (getSDWidget() == null || getSDWidget().isDisposed()) { + return; + } + + getSDWidget().getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + if (getSDWidget() == null || getSDWidget().isDisposed()) { + return; + } + if (wait_) { + if (waitCursor != null && !waitCursor.isDisposed()) { + waitCursor.dispose(); + } + waitCursor = new Cursor(getSDWidget().getDisplay(), SWT.CURSOR_WAIT); + getSDWidget().setCursor(waitCursor); + getSDWidget().getDisplay().update(); + } else { + if (waitCursor != null && !waitCursor.isDisposed()) { + waitCursor.dispose(); + } + waitCursor = null; + getSDWidget().setCursor(null); + getSDWidget().getDisplay().update(); + } + } + }); + } + + /** + * Return the time compression bar widget + * + * @return the time compression bar + */ + public TimeCompressionBar getTimeCompressionBar() { + return timeCompressionBar; + } + + /** + * Returns the current Frame (the sequence diagram container) + * + * @return the frame + */ + public Frame getFrame() { + if (getSDWidget() != null) { + return getSDWidget().getFrame(); + } + else { + return null; + } + } + + protected boolean restoreLoader() { + String id = getViewSite().getId(); + // System.err.println("restoreLoader() id="+id); + if (id == null) { + return true; + } + IUml2SDLoader l = LoadersManager.getInstance().getCurrentLoader(id, this); + // System.err.println("restoreLoader() l="+l); + if ((l != null)) {// &&( LoadersManager.getLoadersManager().getViewer(l)==this)){ + l.setViewer(this); + return false; + } else { + loadBlank(); + return true; + } + } + + protected boolean isViewReady() { + IWorkbenchPage persp = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + if (persp == null) + return false; + + IViewReference[] ref = persp.getViewReferences(); + for (int i = 0; i < ref.length; i++) { + if (ref[i].getView(false) == this) { + return true; + } + } + return false; + } + + protected void createMenuGroup() { + IActionBars bar = getViewSite().getActionBars(); + if (bar == null) { + return; + } + bar.getToolBarManager().add(new Separator("UML2SD_VIEW_MODES")); //$NON-NLS-1$ + bar.getToolBarManager().add(new Separator("UML2SD_WORKING_SET")); //$NON-NLS-1$ + bar.getToolBarManager().add(new Separator("UML2SD_SORTING")); //$NON-NLS-1$ + bar.getToolBarManager().add(new Separator("UML2SD_FILTERING")); //$NON-NLS-1$ + bar.getToolBarManager().add(new Separator("UML2SD_VIEW_LAYOUT")); //$NON-NLS-1$ + bar.getToolBarManager().add(new Separator("UML2SD_LINK_EDITOR")); //$NON-NLS-1$ + bar.getToolBarManager().add(new Separator("UML2SD_OTHER_COMMANDS")); //$NON-NLS-1$ + bar.getToolBarManager().add(new Separator("UML2SD_OTHER_PLUGINS_COMMANDS")); //$NON-NLS-1$ + bar.getMenuManager().add(new Separator("UML2SD_VIEW_MODES")); //$NON-NLS-1$ + bar.getMenuManager().add(new Separator("UML2SD_WORKING_SET")); //$NON-NLS-1$ + bar.getMenuManager().add(new Separator("UML2SD_SORTING")); //$NON-NLS-1$ + bar.getMenuManager().add(new Separator("UML2SD_FILTERING")); //$NON-NLS-1$ + bar.getMenuManager().add(new Separator("UML2SD_VIEW_LAYOUT")); //$NON-NLS-1$ + bar.getMenuManager().add(new Separator("UML2SD_LINK_EDITOR")); //$NON-NLS-1$ + bar.getMenuManager().add(new Separator("UML2SD_OTHER_COMMANDS")); //$NON-NLS-1$ + bar.getMenuManager().add(new Separator("UML2SD_OTHER_PLUGINS_COMMANDS")); //$NON-NLS-1$ + } + + @SuppressWarnings("rawtypes") + @Override + public Object getAdapter(Class _adapter) { + Object obj = super.getAdapter(_adapter); + if (sdPropertiesProvider != null && _adapter.equals(IPropertySheetPage.class)) { + return sdPropertiesProvider.getPropertySheetEntry(); + } + + return obj; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/SDWidget.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/SDWidget.java new file mode 100755 index 0000000000..8e0aa0a30f --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/SDWidget.java @@ -0,0 +1,1713 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: SDWidget.java,v 1.6 2008/12/12 22:21:46 jcayne Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import org.eclipse.jface.contexts.IContextIds; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.ui.ITmfImageConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.BaseMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.BasicExecutionOccurrence; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Frame; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.ITimeRange; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Metrics; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IColor; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDCollapseProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.LoadersManager; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.preferences.SDViewPref; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDPrintDialog; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDPrintDialogUI; +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.ACC; +import org.eclipse.swt.accessibility.Accessible; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleControlAdapter; +import org.eclipse.swt.accessibility.AccessibleControlEvent; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.printing.Printer; +import org.eclipse.swt.printing.PrinterData; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Caret; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.ui.contexts.IContextService; +import org.eclipse.ui.part.ViewPart; + +/** + * + * @author sveyrier + * + */ +public class SDWidget extends ScrollView implements SelectionListener, IPropertyChangeListener, DisposeListener, ITimeCompressionListener { + + protected Frame frame; + protected Image overView = null; + protected MenuItem zoomIn = null; + protected MenuItem zoomOut = null; + protected SDWidgetSelectionProvider selProvider = null; + public float zoomValue = 1; + protected boolean zoomInMode = false; + protected boolean zoomOutMode = false; + + protected List selectedNodeList = null; + protected boolean ctrlSelection = false; + + protected ViewPart site = null; + + public GraphNode currentGraphNode = null; + public GraphNode listStart = null; + public ArrayList prevList = null; + + protected TimeCompressionBar timeBar = null; + + protected DiagramToolTip toolTip = null; + + protected Accessible accessible = null; + + protected GraphNode toolTipNode; + + protected Lifeline dragAndDrop = null; + + protected int focusedWidget = -1; + + protected float printerZoom = 0; + + protected int printerY = 0; + + protected int printerX = 0; + + protected boolean getDragAndDrop = false; + + protected int dragX = 0; + protected int dragY = 0; + + protected boolean reorderMode = false; + + Image collapaseCaretImg = null; + Image arrowUpCaretImg = null; + Image currentCaretImage = null; + + protected ISDCollapseProvider collapseProvider = null; + + protected Caret insertionCartet = null; + + protected ArrayList reorderList = null; + + protected boolean printing = false; + protected Printer printer = null; + + protected boolean shiftSelection = false; + + protected DiagramToolTip scrollToolTip = null; + + public SDWidget(Composite c, int s) { + super(c, s | SWT.NO_BACKGROUND, true); + setOverviewEnabled(true); + selectedNodeList = new ArrayList(); + selProvider = new SDWidgetSelectionProvider(); + SDViewPref.getInstance().addPropertyChangeListener(this); + toolTip = new DiagramToolTip(getViewControl()); + super.addDisposeListener(this); + + scrollToolTip = new DiagramToolTip(c); + getVerticalBar().addListener(SWT.MouseUp, new Listener() { + + @Override + public void handleEvent(Event event) { + scrollToolTip.hideToolTip(); + } + + }); + // tooltip on vertical bar causes trouble when scrolling, because + // tooltip is constantly updated and shown + // getVerticalBar().addSelectionListener(new SelectionListener(){ + // + // public void widgetSelected(SelectionEvent e) { + // scrollToolTip.hideToolTip(); + // double minOcc=0; + // boolean needInit=true; + // GraphNode m=null; + // for (int i=0;i(); + getViewControl().addTraverseListener(new TraverseListener() { + + @Override + public void keyTraversed(TraverseEvent e) { + if ((e.detail == SWT.TRAVERSE_TAB_NEXT) || (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)) + e.doit = true; + } + + }); + + addTraverseListener(new TraverseListener() { + + @Override + public void keyTraversed(TraverseEvent e) { + if ((e.detail == SWT.TRAVERSE_TAB_NEXT) || (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)) + e.doit = true; + } + + }); + + getViewControl().addFocusListener(new FocusListener() { + + @Override + public void focusGained(FocusEvent e) { + // TODO Auto-generated method stub + SDViewPref.getInstance().setNoFocusSelection(false); + ctrlSelection = false; + shiftSelection = false; + redraw(); + } + + @Override + public void focusLost(FocusEvent e) { + SDViewPref.getInstance().setNoFocusSelection(true); + redraw(); + } + }); + } + + public void setTimeBar(TimeCompressionBar bar) { + if (bar != null) { + timeBar = bar; + timeBar.addTimeCompressionListener(this); + } + } + + protected void setCollapseProvider(ISDCollapseProvider provider) { + collapseProvider = provider; + } + + @Override + protected void keyPressedEvent(KeyEvent event) { + if (!(isFocusControl() || getViewControl().isFocusControl())) { + Control[] child = getParent().getChildren(); + for (int i = 0; i < child.length; i++) { + if (child[i].isFocusControl()) { + if (!(child[i] instanceof ScrollView)) { + getViewControl().setFocus(); + break; + } + } + } + } + setFocus(-1); + + if (event.keyCode == SWT.CTRL) + ctrlSelection = true; + if (event.keyCode == SWT.SHIFT) { + shiftSelection = true; + prevList = new ArrayList(); + prevList.addAll(getSelection()); + } + + GraphNode prevNode = getFocusNode(); + + if (event.keyCode == SWT.ARROW_RIGHT) + traverseRight(); + + if (event.keyCode == SWT.ARROW_LEFT) + traverseLeft(); + + if (event.keyCode == SWT.ARROW_DOWN) + traverseDown(); + + if (event.keyCode == SWT.ARROW_UP) + traverseUp(); + + if (event.keyCode == SWT.HOME) + traverseHome(); + + if (event.keyCode == SWT.END) + traverseEnd(); + + if ((!shiftSelection) && (!ctrlSelection)) + listStart = currentGraphNode; + + if (event.character == ' ') { + performSelection(currentGraphNode); + if (!shiftSelection) + listStart = currentGraphNode; + } + + if ((shiftSelection) && (prevNode != getFocusNode())) { + clearSelection(); + addSelection(prevList); + addSelection(frame.getNodeList(listStart, getFocusNode())); + if (getFocusNode() instanceof Lifeline) + ensureVisible(getFocusNode().getX(), getFocusNode().getY(), getFocusNode().getWidth(), getFocusNode().getHeight(), SWT.CENTER | SWT.VERTICAL, true); + else + ensureVisible(getFocusNode()); + } else if ((!ctrlSelection) && (!shiftSelection)) { + + clearSelection(); + if (getFocusNode() != null) { + addSelection(getFocusNode()); + + if (getFocusNode() instanceof Lifeline) + ensureVisible(getFocusNode().getX(), getFocusNode().getY(), getFocusNode().getWidth(), getFocusNode().getHeight(), SWT.CENTER | SWT.VERTICAL, true); + else + ensureVisible(getFocusNode()); + } + } + + if (currentGraphNode != null) + currentGraphNode.setFocused(true); + redraw(); + + if ((event.character == ' ') && ((zoomInMode) || (zoomOutMode))) { + int cx = Math.round((getContentsX() + getVisibleWidth() / 2) / zoomValue); + int cy = Math.round((getContentsY() + getVisibleHeight() / 2) / zoomValue); + if (zoomInMode) { + if (zoomValue < 64) + zoomValue = zoomValue * (float) 1.25; + } else + zoomValue = zoomValue / (float) 1.25; + int x = Math.round(cx * zoomValue - getVisibleWidth() / (float) 2); + int y = Math.round(cy * zoomValue - getVisibleHeight() / (float) 2); + setContentsPos(x, y); + if (timeBar != null) + timeBar.setZoom(zoomValue); + // redraw also resize the scrollView content + redraw(); + } + } + + @Override + protected void keyReleasedEvent(KeyEvent event) { + setFocus(-1); + if (event.keyCode == SWT.CTRL) + ctrlSelection = false; + if (event.keyCode == SWT.SHIFT) + shiftSelection = false; + super.keyReleasedEvent(event); + setFocus(1); + } + + /** + * Resize the contents to insure the frame fit into the view + * + * @param frame the frame which will be drawn in the view + */ + public void resizeContents(Frame frame) { + int width = Math.round((frame.getWidth() + 2 * Metrics.FRAME_H_MARGIN) * zoomValue); + int height = Math.round((frame.getHeight() + 2 * Metrics.FRAME_V_MARGIN) * zoomValue); + resizeContents(width, height); + } + + protected boolean checkFocusOnChilds(Control childs) { + if (childs instanceof Composite) { + Control[] child = ((Composite) childs).getChildren(); + for (int i = 0; i < child.length; i++) { + if (child[i].isFocusControl()) { + return true; + } else + checkFocusOnChilds(child[i]); + } + } + return false; + } + + @Override + public boolean isFocusControl() { + Control[] child = getChildren(); + for (int i = 0; i < child.length; i++) { + if (child[i].isFocusControl()) { + return true; + } else + checkFocusOnChilds(child[i]); + } + return false; + } + + /** + * The frame to render (the sequence diagram) + * + * @param theFrame the frame to display + */ + public void setFrame(Frame theFrame, boolean resetPosition) { + reorderList.clear(); + selectedNodeList.clear(); + selProvider.setSelection(new StructuredSelection()); + frame = theFrame; + if (resetPosition) { + setContentsPos(0, 0); + resizeContents(frame); + redraw(); + } + // prepare the old overview to be reused + if (overView != null) + overView.dispose(); + overView = null; + resizeContents(frame); + } + + /** + * Returns the current Frame (the sequence diagram container) + * + * @return the frame + */ + public Frame getFrame() { + return frame; + } + + /** + * Returns the selection provider for the current sequence diagram + * + * @return the selection provider + */ + public ISelectionProvider getSelectionProvider() { + return selProvider; + } + + @Override + public boolean setContentsPos(int x, int y) { + if (x < 0) + x = 0; + if (y < 0) + y = 0; + if (frame == null) + return false; + if (x + getVisibleWidth() > getContentsWidth()) + x = getContentsWidth() - getVisibleWidth(); + if (y + getVisibleHeight() > getContentsHeight()) + y = getContentsHeight() - getVisibleHeight(); + int x1 = Math.round(x / zoomValue); + int y2 = Math.round(y / zoomValue); + int width = Math.round(getVisibleWidth() / zoomValue); + int height = Math.round(getVisibleHeight() / zoomValue); + frame.updateIndex(x1, y2, width, height); + + if (insertionCartet != null && insertionCartet.isVisible()) + insertionCartet.setVisible(false); + + return super.setContentsPos(x, y); + } + + @Override + protected void contentsMouseHover(MouseEvent event) { + GraphNode graphNode = null; + if (frame != null) { + int x = Math.round(event.x / zoomValue); + int y = Math.round(event.y / zoomValue); + graphNode = frame.getNodeAt(x, y); + if ((graphNode != null) && (SDViewPref.getInstance().tooltipEnabled())) { + toolTipNode = graphNode; + String postfix = getPostfixForTooltip(true); + if (graphNode instanceof Lifeline) { + Lifeline lifeline = (Lifeline) graphNode; + toolTip.showToolTip(lifeline.getToolTipText() + postfix); + setFocus(0); + } else { + toolTip.showToolTip(graphNode.getName() + postfix); + setFocus(0); + } + } else + toolTip.hideToolTip(); + } + } + + protected String getPostfixForTooltip(boolean accessible) { + String postfix = "";//$NON-NLS-1$ + // Determine if the tooltip must show the time difference between the current mouse position and + // the last selected graphNode + if ((currentGraphNode != null) && + (currentGraphNode instanceof ITimeRange) && + (toolTipNode instanceof ITimeRange) && + (currentGraphNode != toolTipNode) && + ((ITimeRange) toolTipNode).hasTimeInfo() && + ((ITimeRange) currentGraphNode).hasTimeInfo()) { + postfix = " -> " + currentGraphNode.getName() + "\n" + SDMessages._138 + " "; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + //double delta = ((ITimeRange)toolTipNode).getLastTime()-((ITimeRange)currentGraphNode).getLastTime(); + TmfTimestamp firstTime = ((ITimeRange) currentGraphNode).getEndTime(); + TmfTimestamp lastTime = ((ITimeRange) toolTipNode).getEndTime(); + TmfTimestamp delta = lastTime.getDelta(firstTime); + postfix += delta.toString(); + } else { + if ((toolTipNode instanceof ITimeRange) && ((ITimeRange) toolTipNode).hasTimeInfo()) { + postfix = "\n";//$NON-NLS-1$ + TmfTimestamp firstTime = ((ITimeRange) toolTipNode).getStartTime(); + TmfTimestamp lastTime = ((ITimeRange) toolTipNode).getEndTime(); + + if (firstTime != null) { + if (lastTime != null && firstTime.compareTo(lastTime, true) != 0) { + postfix += "start: " + firstTime + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ + postfix += "end: " + lastTime + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ + } else { + postfix += firstTime.toString(); + } + } + else if (lastTime != null) { + postfix += lastTime.toString(); + } + } + } + return postfix; + } + + protected void setFocus(int newFocusShape) { + focusedWidget = newFocusShape; + if (focusedWidget == -1) { + getViewControl().getAccessible().setFocus(ACC.CHILDID_SELF); + } else { + getViewControl().getAccessible().setFocus(focusedWidget); + } + } + + /** Timer for auto_scroll feature */ + protected AutoScroll local_auto_scroll_ = null; + /** TimerTask for auto_scroll feature !=null when auto scroll is running */ + protected Timer local_auto_scroll_timer_ = null; + + /** TimerTask for auto scroll feature. */ + protected static class AutoScroll extends TimerTask { + public int dx_, dy_; + public SDWidget sv_; + + public AutoScroll(SDWidget _sv, int _dx, int _dy) { + sv_ = _sv; + dx_ = _dx; + dy_ = _dy; + } + + @Override + public void run() { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + if (sv_.isDisposed()) + return; + sv_.dragX += dx_; + sv_.dragY += dy_; + sv_.scrollBy(dx_, dy_); + } + }); + } + } + + @Override + protected void contentsMouseMoveEvent(MouseEvent e) { + scrollToolTip.hideToolTip(); + toolTip.hideToolTip(); + // super.contentsMouseMoveEvent(e); + if (!(isFocusControl() || getViewControl().isFocusControl())) { + Control[] child = getParent().getChildren(); + for (int i = 0; i < child.length; i++) { + if (child[i].isFocusControl()) { + if (!(child[i] instanceof ScrollView)) { + getViewControl().setFocus(); + break; + } + } + } + } + setFocus(-1); + + if (((e.stateMask & SWT.BUTTON_MASK) != 0) && ((dragAndDrop != null) || getDragAndDrop) && (reorderMode || collapseProvider != null)) { + getDragAndDrop = false; + if (currentGraphNode instanceof Lifeline) + dragAndDrop = (Lifeline) currentGraphNode; + if (dragAndDrop != null) { + int dx = 0; + int dy = 0; + if (e.x > getContentsX() + getVisibleWidth()) { + dx = e.x - (getContentsX() + getVisibleWidth()); + } else if (e.x < getContentsX()) { + dx = -getContentsX() + e.x; + } + if (e.y > getContentsY() + getVisibleHeight()) { + dy = e.y - (getContentsY() + getVisibleHeight()); + } else if (e.y < getContentsY()) { + dy = -getContentsY() + e.y; + } + dragX = e.x; + dragY = e.y; + if (dx != 0 || dy != 0) { + if (local_auto_scroll_ == null) { + if (local_auto_scroll_timer_ == null) { + local_auto_scroll_timer_ = new Timer(true); + } + local_auto_scroll_ = new AutoScroll(this, dx, dy); + local_auto_scroll_timer_.schedule(local_auto_scroll_, 0, 75); + } else { + local_auto_scroll_.dx_ = dx; + local_auto_scroll_.dy_ = dy; + } + } else if (local_auto_scroll_ != null) { + local_auto_scroll_.cancel(); + local_auto_scroll_ = null; + } + dragX = Math.round(e.x / zoomValue); + dragY = Math.round(e.y / zoomValue); + redraw(); + Lifeline node = frame.getCloserLifeline(dragX); + if ((node != null) && (node != dragAndDrop)) { + int y = 0; + int y1 = 0; + int height = Metrics.getLifelineHeaderFontHeigth() + 2 * Metrics.LIFELINE_HEARDER_TEXT_V_MARGIN; + int hMargin = Metrics.LIFELINE_VT_MAGIN / 4; + int x = node.getX(); + int width = node.getWidth(); + if (frame.getVisibleAreaY() < node.getY() + node.getHeight() - height - hMargin) + y = contentsToViewY(Math.round((node.getY() + node.getHeight()) * zoomValue)); + else + y = Math.round(height * zoomValue); + + if (frame.getVisibleAreaY() < contentsToViewY(node.getY() - hMargin)) + y1 = contentsToViewY(Math.round((node.getY() - hMargin) * zoomValue)); + else + y1 = Math.round(height * zoomValue); + + int rx = Math.round(x * zoomValue); + + insertionCartet.setVisible(true); + if ((insertionCartet.getImage() != null) && (!insertionCartet.getImage().isDisposed())) + insertionCartet.getImage().dispose(); + if (rx <= e.x && Math.round(rx + (width * zoomValue)) >= e.x) { + if (collapseProvider != null) { + ImageData data = collapaseCaretImg.getImageData(); + data = data.scaledTo(Math.round(collapaseCaretImg.getBounds().width * zoomValue), Math.round(collapaseCaretImg.getBounds().height * zoomValue)); + currentCaretImage = new Image(Display.getCurrent(), data); + insertionCartet.setImage(currentCaretImage); + insertionCartet.setLocation(contentsToViewX(rx + Math.round((width / (float) 2) * zoomValue)) - currentCaretImage.getBounds().width / 2, y); + } + } else if (reorderMode) { + if (rx > e.x) { + if (node.getIndex() > 1 && frame.getLifeline(node.getIndex() - 2) == dragAndDrop) + return; + ImageData data = arrowUpCaretImg.getImageData(); + data = data.scaledTo(Math.round(arrowUpCaretImg.getBounds().width * zoomValue), Math.round(arrowUpCaretImg.getBounds().height * zoomValue)); + currentCaretImage = new Image(Display.getCurrent(), data); + insertionCartet.setImage(currentCaretImage); + insertionCartet.setLocation(contentsToViewX(Math.round((x - Metrics.LIFELINE_SPACING / 2) * zoomValue)) - currentCaretImage.getBounds().width / 2, y1); + } else { + if (node.getIndex() < frame.lifeLinesCount() && frame.getLifeline(node.getIndex()) == dragAndDrop) + return; + ImageData data = arrowUpCaretImg.getImageData(); + data = data.scaledTo(Math.round(arrowUpCaretImg.getBounds().width * zoomValue), Math.round(arrowUpCaretImg.getBounds().height * zoomValue)); + currentCaretImage = new Image(Display.getCurrent(), data); + insertionCartet.setImage(currentCaretImage); + insertionCartet.setLocation(contentsToViewX(Math.round((x + width + Metrics.LIFELINE_SPACING / 2) * zoomValue)) - currentCaretImage.getBounds().width / 2 + 1, y1); + } + } + } else + insertionCartet.setVisible(false); + } + } else + super.contentsMouseMoveEvent(e); + } + + @Override + protected void contentsMouseUpEvent(MouseEvent event) { + // Just in case the diagram highlight a time compression region + // this region need to be released when clicking everywhere + insertionCartet.setVisible(false); + if (dragAndDrop != null) { + if ((overView != null) && (!overView.isDisposed())) + overView.dispose(); + overView = null; + Lifeline node = frame.getCloserLifeline(dragX); + if (node != null) { + int rx = Math.round(node.getX() * zoomValue); + if (rx <= event.x && Math.round(rx + (node.getWidth() * zoomValue)) >= event.x) { + if ((collapseProvider != null) && (dragAndDrop != node)) + collapseProvider.collapseTwoLifelines((Lifeline) dragAndDrop, node); + } else if (rx < event.x) { + frame.insertLifelineAfter((Lifeline) dragAndDrop, node); + if (node.getIndex() < frame.lifeLinesCount()) { + Lifeline temp[] = { (Lifeline) dragAndDrop, frame.getLifeline(node.getIndex()) }; + reorderList.add(temp); + } else { + Lifeline temp[] = { (Lifeline) dragAndDrop, null }; + reorderList.add(temp); + } + } else { + frame.insertLifelineBefore((Lifeline) dragAndDrop, node); + Lifeline temp[] = { (Lifeline) dragAndDrop, node }; + reorderList.add(temp); + } + } + } + dragAndDrop = null; + redraw(); + if (frame == null) { + return; + } + frame.resetTimeCompression(); + + // reset auto scroll if it's engaged + if (local_auto_scroll_ != null) { + local_auto_scroll_.cancel(); + local_auto_scroll_ = null; + } + super.contentsMouseUpEvent(event); + } + + @Override + protected void contentsMouseDownEvent(MouseEvent event) { + if (currentGraphNode != null) + currentGraphNode.setFocused(false); + // Just in case the diagram highlight a time compression region + // this region need to be released when clicking everywhere + if (frame == null) { + return; + } + frame.resetTimeCompression(); + + if ((event.stateMask & SWT.CTRL) != 0) { + ctrlSelection = true; + } else + ctrlSelection = false; + + if (((zoomInMode) || (zoomOutMode)) && (event.button == 1)) { + int cx = Math.round(event.x / zoomValue); + int cy = Math.round(event.y / zoomValue); + if (zoomInMode) { + if (zoomValue < 64) + zoomValue = zoomValue * (float) 1.25; + } else + zoomValue = zoomValue / (float) 1.25; + int x = Math.round(cx * zoomValue - getVisibleWidth() / (float) 2); + int y = Math.round(cy * zoomValue - getVisibleHeight() / (float) 2); + setContentsPos(x, y); + if (timeBar != null) + timeBar.setZoom(zoomValue); + // redraw also resize the scrollView content + redraw(); + } else// if (event.button ==1) + { + GraphNode node = null; + if (frame != null) { + int x = Math.round(event.x / zoomValue); + int y = Math.round(event.y / zoomValue); + node = frame.getNodeAt(x, y); + + if ((event.button == 1) || ((node != null) && !node.isSelected())) { + if (!shiftSelection) + listStart = node; + if (shiftSelection) { + clearSelection(); + addSelection(frame.getNodeList(listStart, node)); + } else + performSelection(node); + currentGraphNode = node; + if (node != null) + node.setFocused(true); + } + redraw(); + } + } + if (dragAndDrop == null) + super.contentsMouseDownEvent(event); + getDragAndDrop = (event.button == 1); + + } + + /** + * Highlight the given GraphNode
+ * The GraphNode is then displayed using the system default selection color + * + * @param node the GraphNode to highlight + */ + protected void performSelection(GraphNode node) { + if ((ctrlSelection) || (shiftSelection)) { + if (node != null) { + if (selectedNodeList.contains(node)) { + removeSelection(node); + } else { + addSelection(node); + } + } else + return; + } else { + clearSelection(); + if (node != null) { + addSelection(node); + } + } + } + + public List getSelection() { + return selectedNodeList; + } + + public void addSelection(GraphNode node) { + if (node == null) + return; + selectedNodeList.add(node); + node.setSelected(true); + currentGraphNode = node; + StructuredSelection selection = new StructuredSelection(selectedNodeList); + selProvider.setSelection(selection); + } + + public void addSelection(List list) { + // selectedNodeList.addAll(list); + for (int i = 0; i < list.size(); i++) { + if (!selectedNodeList.contains(list.get(i))) { + selectedNodeList.add(list.get(i)); + ((GraphNode) list.get(i)).setSelected(true); + } + } + StructuredSelection selection = new StructuredSelection(selectedNodeList); + selProvider.setSelection(selection); + } + + public void removeSelection(GraphNode node) { + selectedNodeList.remove(node); + node.setSelected(false); + node.setFocused(false); + StructuredSelection selection = new StructuredSelection(selectedNodeList); + selProvider.setSelection(selection); + } + + public void removeSelection(List list) { + selectedNodeList.removeAll(list); + for (int i = 0; i < list.size(); i++) { + ((GraphNode) list.get(i)).setSelected(false); + ((GraphNode) list.get(i)).setFocused(false); + } + StructuredSelection selection = new StructuredSelection(selectedNodeList); + selProvider.setSelection(selection); + } + + /** + * Clear the list of GraphNodes which must be drawn selected + * + */ + public void clearSelection() { + for (int i = 0; i < selectedNodeList.size(); i++) { + ((GraphNode) selectedNodeList.get(i)).setSelected(false); + ((GraphNode) selectedNodeList.get(i)).setFocused(false); + } + currentGraphNode = null; + selectedNodeList.clear(); + selProvider.setSelection(new StructuredSelection()); + } + + public void setSite(ViewPart viewSite) { + site = viewSite; + site.getSite().setSelectionProvider(selProvider); + IContextService service = (IContextService) site.getSite().getWorkbenchWindow().getService(IContextService.class); + service.activateContext("org.eclipse.linuxtools.tmf.ui.view.uml2sd.context"); //$NON-NLS-1$ + service.activateContext(IContextIds.CONTEXT_ID_WINDOW); + } + + protected Image getDrawBuffer(GC gc) { + + update(); + Rectangle area = getClientArea(); + Image dbuffer = null; + GC gcim = null; + + try { + // if (dbuffer!=null) + // dbuffer.dispose(); + dbuffer = new Image(getDisplay(), area.width, area.height); + } catch (Exception e) { + System.out.println(e.toString()); + } + gcim = new GC(dbuffer); + + NGC context = new NGC(this, gcim); + + // Set the metrics to use for lifeline text and message text + // using the Graphical Context + Metrics.setLifelineFontHeight(context.getFontHeight(SDViewPref.getInstance().getFont(SDViewPref.PREF_LIFELINE))); + Metrics.setLifelineFontWidth(context.getFontWidth(SDViewPref.getInstance().getFont(SDViewPref.PREF_LIFELINE))); + Metrics.setLifelineWidth(SDViewPref.getInstance().getLifelineWidth()); + Metrics.setFrameFontHeight(context.getFontHeight(Frame.getUserPref().getFont(ISDPreferences.PREF_FRAME_NAME))); + Metrics.setLifelineHeaderFontHeight(context.getFontHeight(Frame.getUserPref().getFont(ISDPreferences.PREF_LIFELINE_HEADER))); + + int syncMessFontH = context.getFontHeight(SDViewPref.getInstance().getFont(SDViewPref.PREF_SYNC_MESS)); + int syncMessRetFontH = context.getFontHeight(SDViewPref.getInstance().getFont(SDViewPref.PREF_SYNC_MESS_RET)); + int asyncMessFontH = context.getFontHeight(SDViewPref.getInstance().getFont(SDViewPref.PREF_ASYNC_MESS)); + int asyncMessRetFontH = context.getFontHeight(SDViewPref.getInstance().getFont(SDViewPref.PREF_ASYNC_MESS_RET)); + + int messageFontHeight = 0; + if (syncMessFontH > syncMessRetFontH) + messageFontHeight = syncMessFontH; + else + messageFontHeight = syncMessRetFontH; + if (messageFontHeight < asyncMessFontH) + messageFontHeight = asyncMessFontH; + if (messageFontHeight < asyncMessRetFontH) + messageFontHeight = asyncMessRetFontH; + Metrics.setMessageFontHeight(messageFontHeight); + context.setFont(SDViewPref.getInstance().getFont(SDViewPref.PREF_LIFELINE)); + + int width = (int) ((frame.getWidth() + 2 * Metrics.FRAME_H_MARGIN) * zoomValue); + int height = (int) ((frame.getHeight() + 2 * Metrics.FRAME_V_MARGIN) * zoomValue); + // if (width xRatio) { + setOverviewSize((int) (getVisibleHeight() * 0.75)); + } else { + setOverviewSize((int) (getVisibleWidth() * 0.75)); + } + } + + /** + * Returns the GraphNode overView the mouse if any + * + * @return the GraphNode + */ + public GraphNode getMouseOverNode() { + return currentGraphNode; + } + + @Override + public void widgetDefaultSelected(SelectionEvent event) { + } + + @Override + public void widgetSelected(SelectionEvent event) { + if (event.widget == zoomIn) + zoomValue = zoomValue * 2; + else if (event.widget == zoomOut) + zoomValue = zoomValue / 2; + else { + // SearchFilterDialog tt = new SearchFilterDialog(null);//display.getActiveShell()); + } + redraw(); + } + + public void setZoomInMode(boolean value) { + if (value) + setZoomOutMode(false); + zoomInMode = value; + } + + public void setZoomOutMode(boolean value) { + if (value) + setZoomInMode(false); + zoomOutMode = value; + } + + /** + * Moves the Sequence diagram to ensure the given node is visible and draw it selected + * + * @param node the GraphNode to move to + */ + public void moveTo(GraphNode node) { + if (node == null) + return; + clearSelection(); + addSelection(node); + ensureVisible(node); + } + + /** + * Moves the Sequence diagram to ensure the given node is visible + * + * @param node the GraphNode to move to + */ + public void ensureVisible(GraphNode node) { + if (node == null) + return; + int x = Math.round(node.getX() * zoomValue); + int y = Math.round(node.getY() * zoomValue); + int width = Math.round(node.getWidth() * zoomValue); + int height = Math.round(node.getHeight() * zoomValue); + if (node instanceof BaseMessage) { + if (height == 0) { + int header = Metrics.LIFELINE_HEARDER_TEXT_V_MARGIN * 2 + Metrics.getLifelineHeaderFontHeigth(); + height = -Math.round((Metrics.getMessagesSpacing() + header) * zoomValue); + y = y + Math.round(Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT * zoomValue); + } + } + if (node instanceof BasicExecutionOccurrence) { + width = 1; + height = 1; + } + if (node instanceof Lifeline) { + y = getContentsY(); + height = getVisibleHeight(); + } + ensureVisible(x, y, width, height, SWT.CENTER, true); + redraw(); + } + + public float getZoomFactor() { + return zoomValue; + } + + /** + * Called when property changed occurs in the preference page. "PREFOK" is fired when the user press the ok or apply + * button + * + * @param e the PropertyChangeEvent + */ + @Override + public void propertyChange(PropertyChangeEvent e) { + if (frame != null && !isDisposed()) { + frame.resetTimeCompression(); + } + if (e.getProperty().equals("PREFOK")) //$NON-NLS-1$ + { + // Prepare the overview to be reused for the new + // settings (especially the colors) + if (overView != null) + overView.dispose(); + overView = null; + redraw(); + } + } + + @Override + public void widgetDisposed(DisposeEvent e) { + if (overView != null) + overView.dispose(); + super.removeDisposeListener(this); + if ((currentCaretImage != null) && (!currentCaretImage.isDisposed())) + currentCaretImage.dispose(); + if ((arrowUpCaretImg != null) && (!arrowUpCaretImg.isDisposed())) + arrowUpCaretImg.dispose(); + if ((collapaseCaretImg != null) && (!collapaseCaretImg.isDisposed())) + collapaseCaretImg.dispose(); + SDViewPref.getInstance().removePropertyChangeListener(this); + LoadersManager lm = LoadersManager.getInstance(); + if (site != null && site instanceof SDView) { + ((SDView) site).resetProviders(); + if (lm != null) + lm.resetLoader(((SDView) site).getViewSite().getId()); + } + } + + public Image getOverview(Rectangle r) { + float oldzoom = zoomValue; + if ((overView != null) && ((r.width != overView.getBounds().width) || (r.height != overView.getBounds().height))) { + overView.dispose(); + overView = null; + } + if (overView == null) { + int backX = getContentsX(); + int backY = getContentsY(); + setContentsPos(0, 0); + overView = new Image(getDisplay(), r.width, r.height); + GC gcim = new GC(overView); + NGC context = new NGC(this, gcim); + context.setBackground(SDViewPref.getInstance().getBackGroundColor(SDViewPref.PREF_FRAME)); + frame.draw(context); + setContentsPos(backX, backY); + gcim.dispose(); + context.dispose(); + } + zoomValue = oldzoom; + return overView; + } + + @Override + protected void drawOverview(GC gc, Rectangle r) { + float oldzoom = zoomValue; + if (getContentsWidth() > getContentsHeight()) + zoomValue = (float) r.width / (float) getContentsWidth() * oldzoom; + else + zoomValue = (float) r.height / (float) getContentsHeight() * oldzoom; + if ((overView != null) && ((r.width != overView.getBounds().width) || (r.height != overView.getBounds().height))) { + overView.dispose(); + overView = null; + } + if (overView == null) { + int backX = getContentsX(); + int backY = getContentsY(); + setContentsPos(0, 0); + overView = new Image(getDisplay(), r.width, r.height); + GC gcim = new GC(overView); + NGC context = new NGC(this, gcim); + context.setBackground(SDViewPref.getInstance().getBackGroundColor(SDViewPref.PREF_FRAME)); + frame.draw(context); + setContentsPos(backX, backY); + gcim.dispose(); + context.dispose(); + } + if ((overView != null) && (r.width == overView.getBounds().width) && (r.height == overView.getBounds().height)) + gc.drawImage(overView, 0, 0, r.width, r.height, 0, 0, r.width, r.height); + + zoomValue = oldzoom; + + super.drawOverview(gc, r); + } + + @Override + public void deltaSelected(Lifeline lifeline, int startEvent, int nbEvent, IColor color) { + frame.highlightTimeCompression(lifeline, startEvent, nbEvent, color); + ensureVisible(lifeline); + int y1 = lifeline.getY() + lifeline.getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * startEvent; + int y2 = lifeline.getY() + lifeline.getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * (startEvent + nbEvent); + ensureVisible(lifeline.getX(), y1 - (Metrics.getLifelineHeaderFontHeigth() + +2 * Metrics.LIFELINE_HEARDER_TEXT_V_MARGIN), lifeline.getWidth(), y2 - y1 + 3, SWT.CENTER | SWT.VERTICAL, true); + redraw(); + update(); + } + + public void resetZoomFactor() { + int currentX = Math.round(getContentsX() / zoomValue); + int currentY = Math.round(getContentsY() / zoomValue); + zoomValue = 1; + if (timeBar != null && !timeBar.isDisposed()) { + timeBar.setZoom(zoomValue); + } + redraw(); + update(); + setContentsPos(currentX, currentY); + } + + /** + * Enable or disable the lifeline reodering using Drag and Drop + * + * @param mode - true to enable false otherwise + */ + public void setReorderMode(boolean mode) { + reorderMode = mode; + } + + /** + * Return the lifelines reorder sequence (using Drag and Drop) if the the reorder mode is turn on. Each ArryList + * element is of type Lifeline[2] with Lifeline[0] inserted before Lifeline[1] in the diagram + * + * @return - the re-odered sequence + */ + public ArrayList getLifelineReoderList() { + return reorderList; + } + + public void setFocus(GraphNode node) { + if (node == null) + return; + if (currentGraphNode != null) + currentGraphNode.setFocused(false); + currentGraphNode = node; + node.setFocused(true); + ensureVisible(node); + setFocus(0); + } + + public GraphNode getFocusNode() { + return currentGraphNode; + } + + public void traverseRight() { + Object selectedNode = getFocusNode(); + if (selectedNode == null) + traverseLeft(); + GraphNode node = null; + if (selectedNode instanceof BaseMessage) { + if (((BaseMessage) selectedNode).getEndLifeline() != null) + node = frame.getCalledMessage((BaseMessage) selectedNode); + } + if (selectedNode instanceof BasicExecutionOccurrence) { + selectedNode = ((BasicExecutionOccurrence) selectedNode).getLifeline(); + } + if ((node == null) && (selectedNode instanceof Lifeline)) { + // if (selectedNode instanceof BaseMessage) + // { + // if (((BaseMessage)selectedNode).getStartLifeline()!=null) + // selectedNode = ((BaseMessage)selectedNode).getStartLifeline(); + // } + for (int i = 0; i < frame.lifeLinesCount(); i++) { + if ((selectedNode == frame.getLifeline(i)) && (i < frame.lifeLinesCount() - 1)) { + node = frame.getLifeline(i + 1); + break; + } + } + } + if (node != null) { + setFocus(node); + redraw(); + } + } + + public void traverseLeft() { + Object selectedNode = getFocusNode(); + GraphNode node = null; + if (selectedNode instanceof BaseMessage) { + if (((BaseMessage) selectedNode).getStartLifeline() != null) + node = frame.getCallerMessage((BaseMessage) selectedNode); + } + if (selectedNode instanceof BasicExecutionOccurrence) { + selectedNode = ((BasicExecutionOccurrence) selectedNode).getLifeline(); + } + if (node == null) { + if (selectedNode instanceof BaseMessage) { + if (((BaseMessage) selectedNode).getEndLifeline() != null) + selectedNode = ((BaseMessage) selectedNode).getEndLifeline(); + } + for (int i = 0; i < frame.lifeLinesCount(); i++) { + if ((selectedNode == frame.getLifeline(i)) && (i > 0)) { + node = frame.getLifeline(i - 1); + break; + } + } + if ((frame.lifeLinesCount() > 0) && (node == null)) + node = frame.getLifeline(0); + } + if (node != null) { + setFocus(node); + redraw(); + } + } + + public void traverseUp() { + Object selectedNode = getFocusNode(); + if (selectedNode == null) + traverseLeft(); + GraphNode node = null; + if (selectedNode instanceof BaseMessage) + node = frame.getPrevLifelineMessage(((BaseMessage) selectedNode).getStartLifeline(), (BaseMessage) selectedNode); + else if (selectedNode instanceof Lifeline) { + node = frame.getPrevLifelineMessage((Lifeline) selectedNode, null); + if (!(node instanceof Lifeline)) + node = null; + } else if (selectedNode instanceof BasicExecutionOccurrence) { + node = frame.getPrevExecOccurrence((BasicExecutionOccurrence) selectedNode); + if (node == null) + node = ((BasicExecutionOccurrence) selectedNode).getLifeline(); + } + if (node == null) { + if (selectedNode instanceof BaseMessage) { + if (((BaseMessage) selectedNode).getStartLifeline() != null) + node = ((BaseMessage) selectedNode).getStartLifeline(); + } + } + + if (node != null) { + setFocus(node); + redraw(); + } + } + + public void traverseDown() { + Object selectedNode = getFocusNode(); + if (selectedNode == null) + traverseLeft(); + GraphNode node; + if (selectedNode instanceof BaseMessage) + node = frame.getNextLifelineMessage(((BaseMessage) selectedNode).getStartLifeline(), (BaseMessage) selectedNode); + else if (selectedNode instanceof Lifeline) { + // node = frame.getNextLifelineMessage((Lifeline)selectedNode,null); + node = frame.getFirstExecution((Lifeline) selectedNode); + } else if (selectedNode instanceof BasicExecutionOccurrence) { + node = frame.getNextExecOccurrence((BasicExecutionOccurrence) selectedNode); + } else + return; + + if (node != null) { + setFocus(node); + redraw(); + } + } + + public void traverseHome() { + Object selectedNode = getFocusNode(); + if (selectedNode == null) + traverseLeft(); + GraphNode node = null; + /* + * if (selectedNode instanceof BaseMessage) { if (((BaseMessage)selectedNode).getStartLifeline()!=null) node = + * ((BaseMessage)selectedNode).getStartLifeline(); } + */ + if (selectedNode instanceof BaseMessage) { + if (((BaseMessage) selectedNode).getStartLifeline() != null) + node = frame.getNextLifelineMessage(((BaseMessage) selectedNode).getStartLifeline(), null); + else + node = frame.getNextLifelineMessage(((BaseMessage) selectedNode).getEndLifeline(), null); + } else if (selectedNode instanceof Lifeline) + node = frame.getNextLifelineMessage((Lifeline) selectedNode, null); + else if (selectedNode instanceof BasicExecutionOccurrence) { + node = frame.getFirstExecution(((BasicExecutionOccurrence) selectedNode).getLifeline()); + } else { + if (frame.lifeLinesCount() > 0) { + Lifeline lifeline = frame.getLifeline(0); + node = frame.getNextLifelineMessage(lifeline, null); + } + } + + if (node != null) { + setFocus(node); + redraw(); + } + } + + public void traverseEnd() { + Object selectedNode = getFocusNode(); + if (selectedNode == null) + traverseLeft(); + GraphNode node; + if (selectedNode instanceof BaseMessage) + node = frame.getPrevLifelineMessage(((BaseMessage) selectedNode).getStartLifeline(), null); + else if (selectedNode instanceof Lifeline) + node = frame.getPrevLifelineMessage((Lifeline) selectedNode, null); + else if (selectedNode instanceof BasicExecutionOccurrence) { + node = frame.getLastExecOccurrence(((BasicExecutionOccurrence) selectedNode).getLifeline()); + } else { + if (frame.lifeLinesCount() > 0) { + Lifeline lifeline = frame.getLifeline(0); + node = frame.getPrevLifelineMessage(lifeline, null); + } else + return; + } + + if (node != null) { + setFocus(node); + redraw(); + } + } + + public void printUI(SDPrintDialogUI sdPrinter) { + PrinterData data = sdPrinter.getPrinterData(); + + if ((data == null) || (frame == null)) { + return; + } + + printer = new Printer(data); + + String jobName = MessageFormat.format(SDMessages._116, new Object[] { String.valueOf(site.getContentDescription()), String.valueOf(frame.getName()) }); + printer.startJob(jobName); + + GC gc = new GC(printer); + Frame.setUserPref(SDViewPref.getInstance()); + + float lastZoom = zoomValue; + + Rectangle area = getClientArea(); + GC gcim = null; + + gcim = gc; + NGC context = new NGC(this, gcim); + + // Set the metrics to use for lifeline text and message text + // using the Graphical Context + Metrics.setLifelineFontHeight(context.getFontHeight(SDViewPref.getInstance().getFont(SDViewPref.PREF_LIFELINE))); + Metrics.setLifelineFontWidth(context.getFontWidth(SDViewPref.getInstance().getFont(SDViewPref.PREF_LIFELINE))); + Metrics.setLifelineWidth(SDViewPref.getInstance().getLifelineWidth()); + Metrics.setFrameFontHeight(context.getFontHeight(Frame.getUserPref().getFont(ISDPreferences.PREF_FRAME_NAME))); + Metrics.setLifelineHeaderFontHeight(context.getFontHeight(Frame.getUserPref().getFont(ISDPreferences.PREF_LIFELINE_HEADER))); + + int syncMessFontH = context.getFontHeight(SDViewPref.getInstance().getFont(SDViewPref.PREF_SYNC_MESS)); + int syncMessRetFontH = context.getFontHeight(SDViewPref.getInstance().getFont(SDViewPref.PREF_SYNC_MESS_RET)); + int asyncMessFontH = context.getFontHeight(SDViewPref.getInstance().getFont(SDViewPref.PREF_ASYNC_MESS)); + int asyncMessRetFontH = context.getFontHeight(SDViewPref.getInstance().getFont(SDViewPref.PREF_ASYNC_MESS_RET)); + + int messageFontHeight = 0; + if (syncMessFontH > syncMessRetFontH) + messageFontHeight = syncMessFontH; + else + messageFontHeight = syncMessRetFontH; + if (messageFontHeight < asyncMessFontH) + messageFontHeight = asyncMessFontH; + if (messageFontHeight < asyncMessRetFontH) + messageFontHeight = asyncMessRetFontH; + Metrics.setMessageFontHeight(messageFontHeight); + context.setFont(SDViewPref.getInstance().getFont(SDViewPref.PREF_LIFELINE)); + + int width = Math.round((frame.getWidth() + 2 * Metrics.FRAME_H_MARGIN) * zoomValue); + int height = Math.round((frame.getHeight() + 2 * Metrics.FRAME_V_MARGIN) * zoomValue); + if (width < area.width) + width = area.width; + if (height < area.height) + height = area.height; + resizeContents(width, height); + + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_FRAME)); + context.fillRectangle(0, 0, getContentsWidth(), Metrics.FRAME_V_MARGIN); + context.fillRectangle(0, 0, frame.getX(), getContentsHeight()); + context.fillRectangle(frame.getX() + frame.getWidth() + 1, 0, getContentsWidth() - (frame.getX() + frame.getWidth() + 1), getContentsHeight()); + context.fillRectangle(0, frame.getY() + frame.getHeight() + 1, getContentsWidth(), getContentsHeight() - (frame.getY() + frame.getHeight() + 1)); + gcim.setLineWidth(1); + + printer.startPage(); + zoomValue = lastZoom; + + int restoreX = getContentsX(); + int restoreY = getContentsY(); + + float zh = getContentsHeight(); + float zw = getContentsWidth(); + + zh = sdPrinter.getStepY() * sdPrinter.getZoomFactor(); + zw = sdPrinter.getStepX() * sdPrinter.getZoomFactor(); + + float zoomValueH = printer.getClientArea().height / zh; + float zoomValueW = printer.getClientArea().width / zw; + if (zoomValueH > zoomValueW) + printerZoom = zoomValueH; + else + printerZoom = zoomValueW; + + if (sdPrinter.printSelection()) { + int[] pagesList = sdPrinter.getPageList(); + + for (int pageIndex = 0; pageIndex < pagesList.length; pageIndex++) { + printPage(pagesList[pageIndex], sdPrinter, context); + } + } else if (sdPrinter.printAll()) { + for (int pageIndex = 1; pageIndex <= sdPrinter.maxNumOfPages(); pageIndex++) { + printPage(pageIndex, sdPrinter, context); + } + } else if (sdPrinter.printCurrent()) { + printPage(getContentsX(), getContentsY(), sdPrinter, context, 1); + } else if (sdPrinter.printRange()) { + for (int pageIndex = sdPrinter.getFrom(); pageIndex <= sdPrinter.maxNumOfPages() && pageIndex <= sdPrinter.getTo(); pageIndex++) { + printPage(pageIndex, sdPrinter, context); + } + } + + printer.endJob(); + printing = false; + + gc.dispose(); + context.dispose(); + + zoomValue = lastZoom; + printer.dispose(); + setContentsPos(restoreX, restoreY); + + } + + public void print() { + SDPrintDialog sdPrinter = new SDPrintDialog(this.getShell(), this); + try { + if (sdPrinter.open() != 0) + return; + } catch (Exception e) { + + } + + printUI(sdPrinter.getDialogUI()); + } + + public void printPage(int pageNum, SDPrintDialogUI pd, NGC context) { + int j = pageNum / pd.getNbRow(); + int i = pageNum % pd.getNbRow(); + if (i != 0) + j++; + else + i = pd.getNbRow(); + + i--; + j--; + + i = (int) (i * pd.getStepX()); + j = (int) (j * pd.getStepY()); + + printPage(i, j, pd, context, pageNum); + + printer.endPage(); + } + + public void printPage(int i, int j, SDPrintDialogUI pd, NGC context, int pageNum) { + printing = false; + int pageNumFontZoom = printer.getClientArea().height / getVisibleHeight(); + printerX = i; + printerY = j; + setContentsPos(i, j); + update(); + printing = true; + float lastZoom = zoomValue; + zoomValue = printerZoom * lastZoom; + + frame.draw(context); + + zoomValue = pageNumFontZoom; + context.setFont(SDViewPref.getInstance().getFont(SDViewPref.PREF_LIFELINE)); + String currentPageNum = String.valueOf(pageNum); + int ii = context.textExtent(currentPageNum); + int jj = context.getCurrentFontHeight(); + // context.setBackground(ColorImpl.getSystemColor(SWT.COLOR_BLACK)); + // context.setForeground(ColorImpl.getSystemColor(SWT.COLOR_WHITE)); + zoomValue = printerZoom * lastZoom; + context.drawText(currentPageNum, Math.round(printerX + getVisibleWidth() / printerZoom - ii / printerZoom), Math.round(printerY + getVisibleHeight() / printerZoom - jj / printerZoom), false); + printing = false; + zoomValue = lastZoom; + } + + @Override + public int getVisibleWidth() { + if (printing) + return printer.getClientArea().width; + return super.getVisibleWidth(); + } + + @Override + public int getVisibleHeight() { + if (printing) + return printer.getClientArea().height; + return super.getVisibleHeight(); + } + + @Override + public int contentsToViewX(int _x) { + if (printing) { + int v = Math.round(printerX * printerZoom); + return _x - v; + } + return _x - getContentsX(); + } + + @Override + public int contentsToViewY(int _y) { + if (printing) { + int v = Math.round(printerY * printerZoom); + return _y - v; + } + return _y - getContentsY(); + } + + @Override + public int getContentsX() { + if (printing) + return Math.round(printerX * printerZoom); + return super.getContentsX(); + + } + + @Override + public int getContentsY() { + if (printing) + return Math.round(printerY * printerZoom); + return super.getContentsY(); + } + + public Printer getPrinter() { + return printer; + } + + public boolean isPrinting() { + return printing; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/SDWidgetSelectionProvider.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/SDWidgetSelectionProvider.java new file mode 100755 index 0000000000..4978b6aab1 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/SDWidgetSelectionProvider.java @@ -0,0 +1,89 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: SDWidgetSelectionProvider.java,v 1.3 2008/01/24 02:29:01 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd; + +import java.util.ArrayList; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; + +/** + * Informs all registered listeners of graph node selection change in the Frame + * + * @author sveyrier + * + */ +public class SDWidgetSelectionProvider implements ISelectionProvider { + + /** + * The listener list + */ + protected ArrayList listenerList = null; + + /** + * The current selection + */ + protected ISelection currentSelection = null; + + protected SDWidgetSelectionProvider() { + listenerList = new ArrayList(); + } + + /** + * Adds the given listener from the selection change listener list + * + * @param listener the listener to add + */ + @Override + public void addSelectionChangedListener(ISelectionChangedListener listener) { + if (!listenerList.contains(listener)) + listenerList.add(listener); + } + + /** + * Removes the given listener from the selection change listener list + * + * @param listener the listener to remove + */ + @Override + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + listenerList.remove(listener); + } + + /** + * Changes the selection to the given selection and inform all listener + * + * @param selection the new current selection + */ + @Override + public void setSelection(ISelection selection) { + currentSelection = selection; + for (int i = 0; i < listenerList.size(); i++) { + ISelectionChangedListener list = (ISelectionChangedListener) listenerList.get(i); + list.selectionChanged(new SelectionChangedEvent(this, currentSelection)); + } + } + + /** + * Returns the current selection + * + * @return the current selection + */ + @Override + public ISelection getSelection() { + return currentSelection; + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/ScrollView.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/ScrollView.java new file mode 100755 index 0000000000..49c5be3d86 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/ScrollView.java @@ -0,0 +1,1757 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ScrollView.java,v 1.3 2008/01/24 02:29:01 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd; + +import java.util.Timer; +import java.util.TimerTask; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.MouseTrackListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.TypedEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.PaletteData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.ScrollBar; +import org.eclipse.swt.widgets.Scrollable; +import org.eclipse.swt.widgets.Shell; + +/** + * ScrollView widget provides a scrolling area with on-demand scroll bars. Overview scrollable panel can be use (@see + * setOverviewEnabled()). + * + * @author Eric Miravete + * @version 1.0 + */ +public class ScrollView extends Composite { + /** Value for scroll bar mode, default is AUTO */ + public static final int AUTO = 0; + public static final int ALWAYS_OFF = 2; + public static final int ALWAYS_ON = 1; + + protected int contents_height_ = 0; + protected int contents_width_ = 0; + protected int contents_x_ = 0; + protected int contents_y_ = 0; + protected int h_scrollbar_mode_ = AUTO; + protected int v_scrollbar_mode_ = AUTO; + protected int h_scrollbar_increment_ = 10; + protected int v_scrollbar_increment_ = 10; + protected boolean auto_scroll_enabled_ = true; + protected int auto_scroll_period_ = 75; + + protected PaintListener localPaintListener = null; + protected MouseMoveListener localMouseMoveListener = null; + protected MouseListener localMouseListener = null; + protected ControlListener localControlListener = null; + protected KeyListener localKeyListener = null; + + // Canvas for vertical/horz SB only ... because new ScrollBar() does works. + protected Canvas vertsb_, horzsb_; // parcequ'on peut pas creer de SB !!! + protected Canvas viewcontrol_; + /** control used in the bottom right corner @see setCornerControl() and @see setOverviewEnabled(true) */ + protected Control corner_control_; + protected int overview_size_ = 100; // default size for overview + + /** + * Create a ScrollView, child of composite c. Both scroll bar have the mode AUTO. Auto scroll feature is enabled + * using a delay of 250ms. Overview feature is not enabled by default (use setOverviewEnabled()). + * + * @param c + * @param style SWT style bits @see SWT + */ + public ScrollView(Composite c, int style) { + this(c, style, true); + } + + /** + * Create a ScrollView, child of composite c. Both scroll bar have the mode AUTO. Auto scroll feature is enabled + * using a delay of 250ms. Overview feature is not enabled by default (use setOverviewEnabled()). + * + * @param c + * @param style SWT style bits @see SWT + * @param mouseWheel force scrollView to handles mouse wheel + */ + public ScrollView(Composite c, int style, boolean mouseWheel) { + super(c, SWT.NONE); // style&(~(SWT.H_SCROLL|SWT.V_SCROLL))); + + horzsb_ = new Canvas(this, SWT.H_SCROLL); + if (mouseWheel) + // force scroll bar to get mouse wheel, those scrollbar will be hidden + viewcontrol_ = new Canvas(this, style | SWT.H_SCROLL | SWT.V_SCROLL); + else + viewcontrol_ = new Canvas(this, style); + viewcontrol_.setBackground(getBackground()); + // hide scroll bar as their are replaced by vertsb_ and horzsb_. + if (mouseWheel) { + viewcontrol_.getVerticalBar().setVisible(false); + viewcontrol_.getHorizontalBar().setVisible(false); + } + vertsb_ = new Canvas(this, SWT.V_SCROLL); + // make vertsb_ able to receive mouse wheel + // doesnot help as we can't set a MouseListener on vertsb_.getVerticalBar() + // to set focus on viewcontrol_ + // vertsb_.addKeyListener( new KeyAdapter() {}); + + setLayout(new SVLayout()); + + localPaintListener = new PaintListener() { + @Override + public void paintControl(PaintEvent event) { + // use clipping, to reduce cost of paint. + Rectangle r = event.gc.getClipping(); + int cx = viewToContentsX(r.x); + int cy = viewToContentsY(r.y); + drawContents(event.gc, cx, cy, r.width, r.height); + } + }; + viewcontrol_.addPaintListener(localPaintListener); + + localMouseMoveListener = new MouseMoveListener() { + @Override + public void mouseMove(MouseEvent e) { + int ox = e.x, oy = e.y; + e.x = viewToContentsX(e.x); + e.y = viewToContentsY(e.y); + contentsMouseMoveEvent(e); + e.x = ox; + e.y = oy; + } + }; + + viewcontrol_.addMouseMoveListener(localMouseMoveListener); + + MouseTrackListener localMouseTrackListener = new MouseTrackListener() { + @Override + public void mouseEnter(MouseEvent e) { + int ox = e.x, oy = e.y; + e.x = viewToContentsX(e.x); + e.y = viewToContentsY(e.y); + contentsMouseEnter(e); + e.x = ox; + e.y = oy; + } + + @Override + public void mouseHover(MouseEvent e) { + int ox = e.x, oy = e.y; + e.x = viewToContentsX(e.x); + e.y = viewToContentsY(e.y); + contentsMouseHover(e); + e.x = ox; + e.y = oy; + } + + @Override + public void mouseExit(MouseEvent e) { + int ox = e.x, oy = e.y; + e.x = viewToContentsX(e.x); + e.y = viewToContentsY(e.y); + contentsMouseExit(e); + e.x = ox; + e.y = oy; + } + + }; + + viewcontrol_.addMouseTrackListener(localMouseTrackListener); + + localMouseListener = new MouseListener() { + @Override + public void mouseDoubleClick(MouseEvent e) { + int ox = e.x, oy = e.y; + e.x = viewToContentsX(e.x); + e.y = viewToContentsY(e.y); + contentsMouseDoubleClickEvent(e); + e.x = ox; + e.y = oy; + } + + @Override + public void mouseDown(MouseEvent e) { + int ox = e.x, oy = e.y; + e.x = mouse_down_x_ = viewToContentsX(e.x); + e.y = mouse_down_y_ = viewToContentsY(e.y); + contentsMouseDownEvent(e); + e.x = ox; + e.y = oy; + } + + @Override + public void mouseUp(MouseEvent e) { + int ox = e.x, oy = e.y; + e.x = viewToContentsX(e.x); + e.y = viewToContentsY(e.y); + contentsMouseUpEvent(e); + e.x = ox; + e.y = oy; + // here because class extenting me can catch mouse Up and want to scroll... + mouse_down_x_ = mouse_down_y_ = -1; + } + }; + viewcontrol_.addMouseListener(localMouseListener); + + localKeyListener = new KeyListener() { + @Override + public void keyPressed(KeyEvent e) { + keyPressedEvent(e); + } + + @Override + public void keyReleased(KeyEvent e) { + keyReleasedEvent(e); + } + }; + // addKeyListener(localKeyListener); + viewcontrol_.addKeyListener(localKeyListener); + + getVerticalBar().addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + setContentsPos(contents_x_, getVerticalBar().getSelection()); + // need to change "hidden" vertical bar value ? + // force focus on viewcontrol_ so we got future mouse wheel's scroll events + if (!viewcontrol_.isFocusControl()) + viewcontrol_.setFocus(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + + if (viewcontrol_.getVerticalBar() != null) + // add viewcontrol hidden scrollbar listener to get mouse wheel ... + viewcontrol_.getVerticalBar().addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + ScrollBar b = viewcontrol_.getVerticalBar(); + setContentsPos(contents_x_, b.getSelection()); + // change "real" vertical bar selection too + getVerticalBar().setSelection(b.getSelection()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + getHorizontalBar().addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + setContentsPos(getHorizontalBar().getSelection(), contents_y_); + // need to change "real" horizontal bar too ? + // force focus on viewcontrol_ so we got future mouse wheel's scroll events + if (!viewcontrol_.isFocusControl()) + viewcontrol_.setFocus(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + if (viewcontrol_.getHorizontalBar() != null) + viewcontrol_.getHorizontalBar().addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + ScrollBar b = viewcontrol_.getHorizontalBar(); + setContentsPos(b.getSelection(), contents_y_); + // change "real" vertical bar selection too + getHorizontalBar().setSelection(b.getSelection()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + } + + @Override + public boolean setFocus() { + return viewcontrol_.forceFocus(); + } + + @Override + public void setCursor(Cursor cursor) { + viewcontrol_.setCursor(cursor); + } + + // dispose controls used in scroll view + @Override + public void dispose() { + if (auto_scroll_ != null) { + auto_scroll_.cancel(); + auto_scroll_ = null; + } + if (viewcontrol_ != null) + viewcontrol_.dispose(); + viewcontrol_ = null; + if (vertsb_ != null) + vertsb_.dispose(); + vertsb_ = null; + if (horzsb_ != null) + horzsb_.dispose(); + horzsb_ = null; + if (corner_control_ != null) { + Object data = corner_control_.getData(); + if (data instanceof Overview) { + ((Overview) data).dispose(); + } + corner_control_.dispose(); + corner_control_ = null; + } + super.dispose(); + } + + /** @return client area of scrollview */ + @Override + public Rectangle getClientArea() { + return viewcontrol_.getClientArea(); + } + + /** change background color of client area */ + @Override + public void setBackground(Color c) { + super.setBackground(c); + viewcontrol_.setBackground(c); + } + + @Override + public void setToolTipText(String text) { + viewcontrol_.setToolTipText(text); + } + + /** + * Draw overview area, @see setOverviewEnabled. By default draw a rectangle corresponding to the visible area of + * scroll view. You can redefine this method to draw the contents as drawContents does... ...in an other magnify + * factor. + * + * @param gc GC to used to draw. + * @param r Rectangle corresponding to the client area of overview. + */ + protected void drawOverview(GC gc, Rectangle r) { + int x = (int) (r.width * contents_x_ / (float) contents_width_); + int y = (int) (r.height * contents_y_ / (float) contents_height_); + int vw = getVisibleWidth(); + int vh = getVisibleHeight(); + int w = r.width - 1; + if (contents_width_ > vw) + w = (int) (r.width * vw / (float) contents_width_); + int h = r.height - 1; + if (contents_height_ > vh) + h = (int) (r.height * vh / (float) contents_height_); + + gc.setForeground(getForeground()); + // too small rectangle ? + if (w < 5 || h < 5) { + // use a cross ... + gc.drawLine(x, 0, x, r.height); + gc.drawLine(0, y, r.width, y); + } else { + gc.drawRectangle(x, y, w, h); + } + } + + /** + * Remove the local Listener and add the new listener. + * + * @param nlistener the new listener + */ + public void replaceControlListener(ControlListener nlistener) { + if (localControlListener != null) { + removeControlListener(localControlListener); + localControlListener = null; + } + addControlListener(nlistener); + } + + /** + * Remove the local Listener and add the new listener. + * + * @param nlistener the new listener + */ + public void replaceKeyListener(KeyListener nlistener) { + if (localKeyListener != null) { + removeKeyListener(localKeyListener); + localKeyListener = null; + } + addKeyListener(nlistener); + } + + /** + * Remove the local Listener and add the new listener. + * + * @param nlistener the new listener + */ + public void replaceMouseListener(MouseListener nlistener) { + if (localMouseListener != null) { + removeMouseListener(localMouseListener); + localMouseListener = null; + } + viewcontrol_.addMouseListener(nlistener); + } + + /** + * Remove the local Listener and add the new listener. + * + * @param nlistener the new listener + */ + public void replaceMouseMoveListener(MouseMoveListener nlistener) { + if (localMouseMoveListener != null) { + removeMouseMoveListener(localMouseMoveListener); + localMouseMoveListener = null; + } + viewcontrol_.addMouseMoveListener(nlistener); + } + + /** + * Remove the local Listener and add the new listener. + * + * @param nlistener the new listener + */ + public void replacePaintListener(PaintListener nlistener) { + if (localPaintListener != null) { + removePaintListener(localPaintListener); + localPaintListener = null; + } + viewcontrol_.addPaintListener(nlistener); + } + + /** + * Access method for the contentsHeight property. + * + * @return the current value of the contentsHeight property + */ + public int getContentsHeight() { + return contents_height_; + } + + /** + * Access method for the contentsWidth property. + * + * @return the current value of the contentsWidth property + */ + public int getContentsWidth() { + return contents_width_; + } + + /** + * Access method for the contentsX property. + * + * @return the current value of the contentsX property + */ + public int getContentsX() { + return contents_x_; + } + + /** + * Access method for the contentsY property. + * + * @return the current value of the contentsY property + */ + public int getContentsY() { + return contents_y_; + } + + /** + * Determines if the dragAutoScroll property is true. + * + * @return true if the dragAutoScroll property is true + */ + public boolean getDragAutoScroll() { + return auto_scroll_enabled_; + } + + /** + * Sets the value of the dragAutoScroll property. + * + * @param aDragAutoScroll the new value of the dragAutoScroll property + */ + public void setDragAutoScroll(boolean aDragAutoScroll) { + auto_scroll_enabled_ = aDragAutoScroll; + if (!auto_scroll_enabled_ && (auto_scroll_ != null)) { + auto_scroll_.cancel(); + auto_scroll_ = null; + } + } + + /** + * Change delay (in millisec) used for auto scroll feature. + * + * @param _period new period between to auto scroll + */ + public void setDragAutoScrollPeriod(int _period) { + auto_scroll_period_ = Math.max(0, _period); + } + + /** + * Return auto scroll period. + */ + public int getDragAutoScrollPeriod() { + return auto_scroll_period_; + } + + /** + * Access method for the hScrollBarMode property. + * + * @return the current value of the hScrollBarMode property + */ + public int getHScrollBarMode() { + return h_scrollbar_mode_; + } + + /** + * Sets the value of the hScrollBarMode property. + * + * @param aHScrollBarMode the new value of the hScrollBarMode property + */ + public void setHScrollBarMode(int aHScrollBarMode) { + h_scrollbar_mode_ = aHScrollBarMode; + } + + /** + * Access method for the vScrollBarMode property. + * + * @return the current value of the vScrollBarMode property + */ + public int getVScrollBarMode() { + return v_scrollbar_mode_; + } + + /** + * Sets the value of the vScrollBarMode property. + * + * @param aVScrollBarMode the new value of the vScrollBarMode property + */ + public void setVScrollBarMode(int aVScrollBarMode) { + v_scrollbar_mode_ = aVScrollBarMode; + } + + /** + * Return horizontal scroll bar increment, default:1 + */ + public int getHScrollBarIncrement() { + return h_scrollbar_increment_; + } + + /** + * Return vertical scroll bar increment, default:1 + */ + public int getVScrollBarIncrement() { + return v_scrollbar_increment_; + } + + /** + * Change horizontal scroll bar increment, minimum:1. Page increment is always set to visible width. + */ + public void setHScrollBarIncrement(int _inc) { + h_scrollbar_increment_ = Math.max(1, _inc); + } + + /** + * Change vertical scroll bar increment, minimum:1. Page increment is always set to visible height. + */ + public void setVScrollBarIncrement(int _inc) { + v_scrollbar_increment_ = Math.max(1, _inc); + } + + /** + * Enable or disable overview feature. Enabling overview, dispose and replace existing corner control by a button. + * Clicking in it open overview, move mouse cursor holding button to move scroll view and release button to hide + * overview. Tip: hold control and/or shift key while moving mouse when overview is open made fine scroll. + * + * @param _b true to engage overview feature + */ + public void setOverviewEnabled(boolean _b) { + if (getOverviewEnabled() == _b) + return; + + Control cc = null; + if (_b) { + Button b = new Button(this, SWT.NONE); + b.setText("+"); //$NON-NLS-1$ + Overview ovr = new Overview(); + ovr.useControl(b); + b.setData(ovr); + cc = b; + b.setToolTipText(SDMessages._78); + } + setCornerControl(cc); + } + + /** + * Change overview size (at ratio 1:1), default is 100 + */ + public void setOverviewSize(int _size) { + overview_size_ = Math.abs(_size); + } + + /** + * @return true is overview feature is enabled + */ + public boolean getOverviewEnabled() { + if (corner_control_ instanceof Button) { + Object data = ((Button) corner_control_).getData(); + // overview alreay + if (data instanceof Overview) + return true; + } + return false; + } + + /** + * @return current overview size at ratio 1:1 + */ + public int getOverviewSize() { + return overview_size_; + } + + /** + * @return control used to display view (might not be this object). use this control to add/remove listener on the + * draw area + */ + public Control getViewControl() { + return viewcontrol_; + } + + /** + * Called when the mouse enter the ScrollView area + * + * @param e + */ + protected void contentsMouseExit(MouseEvent e) { + } + + /** + * Called when the mouse enter the ScrollView area after and system defined time + * + * @param e + */ + protected void contentsMouseHover(MouseEvent e) { + } + + /** + * Called when the mouse enter the ScrollView area + * + * @param e + */ + protected void contentsMouseEnter(MouseEvent e) { + } + + /** + * Called when user double on contents area. + * + * @param e + */ + protected void contentsMouseDoubleClickEvent(MouseEvent e) { + } + + /** + * Called when mouse is on contents area and button is pressed. + * + * @param e + */ + protected void contentsMouseDownEvent(MouseEvent e) { + mouse_down_x_ = e.x; + mouse_down_y_ = e.y; + } + + /** where mouse down appear on contents area */ + protected int mouse_down_x_ = -1, mouse_down_y_ = -1; + + /** TimerTask for auto scroll feature. */ + protected static class AutoScroll extends TimerTask { + public int dx_, dy_; + public ScrollView sv_; + + public AutoScroll(ScrollView _sv, int _dx, int _dy) { + sv_ = _sv; + dx_ = _dx; + dy_ = _dy; + } + + @Override + public void run() { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + sv_.scrollBy(dx_, dy_); + } + }); + } + } + + /** Timer for auto_scroll feature */ + protected AutoScroll auto_scroll_ = null; + /** TimerTask for auto_scroll feature !=null when auto scroll is running */ + protected Timer auto_scroll_timer_ = null; + + /** + * Called when mouse is on contents area and mode. + * + * @param _event + */ + protected void contentsMouseMoveEvent(MouseEvent _event) { + if ((_event.stateMask & SWT.BUTTON_MASK) != 0) { + if (!auto_scroll_enabled_) { + scrollBy(-(_event.x - mouse_down_x_), -(_event.y - mouse_down_y_)); + return; + } + + int sx = 0, sy = 0; + + int v_right = getContentsX() + getVisibleWidth(); + int v_bottom = getContentsY() + getVisibleHeight(); + + // auto scroll... ? + if (_event.x < getContentsX()) { + sx = (getContentsX() - _event.x); + mouse_down_x_ = getContentsX(); + } else if (_event.x > v_right) { + sx = -_event.x + v_right; + mouse_down_x_ = v_right; + } + if (_event.y < getContentsY()) { + sy = (getContentsY() - _event.y); + mouse_down_y_ = getContentsY(); + } else if (_event.y > v_bottom) { + sy = -_event.y + v_bottom; + mouse_down_y_ = v_bottom; + } + + if (sx != 0 || sy != 0) { + // start auto scroll... + if (auto_scroll_ == null) { + if (auto_scroll_timer_ == null) { + auto_scroll_timer_ = new Timer(true); + } + auto_scroll_ = new AutoScroll(this, sx, sy); + auto_scroll_timer_.schedule(auto_scroll_, 0, auto_scroll_period_); + } else { + auto_scroll_.dx_ = sx; + auto_scroll_.dy_ = sy; + } + } else { + if (auto_scroll_ != null) { + auto_scroll_.cancel(); + auto_scroll_ = null; + } + + scrollBy(-(_event.x - mouse_down_x_), -(_event.y - mouse_down_y_)); + } + } + } + + /** + * Called when mouse is on contents area and button is released + * + * @param _event + */ + protected void contentsMouseUpEvent(MouseEvent _event) { + // reset auto scroll if it's engaged + if (auto_scroll_ != null) { + auto_scroll_.cancel(); + auto_scroll_ = null; + } + } + + /** + * Responsible to draw contents area. At least rectangle clipX must be redrawn. This rectangle is given in contents + * coordinates. By default, no paint is produced. + * + * @param gc + * @param clipx + * @param clipy + * @param clipw + * @param cliph + */ + protected void drawContents(GC gc, int clipx, int clipy, int clipw, int cliph) { + } + + /** + * Change the size of the contents area. + * + * @param w new width of the area. + * @param h new height of the area. + */ + public void resizeContents(int _w, int _h) { + // System.out.println("SV--resizeContents("+_w+","+_h+" ) {"); + if (_w < 0) + _w = 0; + if (_h < 0) + _h = 0; + + int oldW = contents_width_; + int oldH = contents_height_; + + if (_w == oldW && _h == oldH) + return; + + // System.out.println("RESIZE-CONTENTS("+_w+","+_h+")"); + contents_width_ = _w; + contents_height_ = _h; + + if (oldW > _w) { + int s = _w; + _w = oldW; + oldW = s; + } + + int vis_width = getVisibleWidth(); + int vis_height = getVisibleHeight(); + if (oldW < vis_width) { + if (_w > vis_width) { + _w = vis_width; + } + viewcontrol_.redraw(getContentsX() + oldW, 0, _w - oldW, vis_height, true); + } + + if (oldH > _h) { + int s = _h; + _h = oldH; + oldH = s; + } + + if (oldH < vis_height) { + if (_h > vis_height) { + _h = vis_height; + } + viewcontrol_.redraw(0, getContentsY() + oldH, vis_width, _h - oldH, true); + } + if (updateScrollBarVisiblity()) { + layout(); + } else { + updateScrollBarsValues(); + } + // System.out.println("SV--resizeContents() }"); + } + + // redefined for internal use .. + @Override + public void redraw() { + super.redraw(); + // ..need to redraw this already: + viewcontrol_.redraw(); + } + + /** + * @param _dx + * @param _dy + */ + public void scrollBy(int _dx, int _dy) { + setContentsPos(getContentsX() + _dx, getContentsY() + _dy); + } + + /** + * Scroll to ensure point(in contents coordinates) is visible. + */ + public void ensureVisible(int _px, int _py) { + int cx = getContentsX(), cy = getContentsY(); + int right = getContentsX() + getVisibleWidth(); + int bottom = getContentsY() + getVisibleHeight(); + if (_px < getContentsX()) { + cx = _px; + } else if (_px > right) { + cx = _px - getVisibleWidth(); + } + if (_py < getContentsY()) { + cy = _py; + } else if (_py > bottom) { + cy = _py - getVisibleHeight(); + } + setContentsPos(cx, cy); + } + + /** + * Make rectangle (_x,_y,_w,_h, in contents coordinates) visible. if rectangle cannot be completely visible, use + * _align flags. + * + * @param _x x contents coordinates of rectangle. + * @param _y y contents coordinates of rectangle. + * @param _w width of rectangle. + * @param _h height of rectangle. + * @param _align bit or'ed SWT flag like SWT.LEFT,RIGHT,CENTER,TOP,BOTTOM,VERTICAL used only for bigger rectangle + * than visible area. By default CENTER/VERTICAL + */ + public void ensureVisible(int _x, int _y, int _w, int _h, int _align) { + ensureVisible(_x, _y, _w, _h, _align, false); + } + + /** + * Make rectangle (_x,_y,_w,_h, in contents coordinates) visible. if rectangle cannot be completely visible, use + * _align flags. + * + * @param _x x contents coordinates of rectangle. + * @param _y y contents coordinates of rectangle. + * @param _w width of rectangle. + * @param _h height of rectangle. + * @param _align bit or'ed SWT flag like SWT.LEFT,RIGHT,CENTER,TOP,BOTTOM,VERTICAL used only for bigger rectangle + * than visible area. By default CENTER/VERTICAL + * @param forceAlign force alignment for rectangle smaller than the visible area + */ + protected void ensureVisible(int _x, int _y, int _w, int _h, int _align, boolean forceAlign) { + if (_w < 0) { + _x = _x + _w; + _w = -_w; + } + if (_h < 0) { + _y = _y + _h; + _h = -_h; + } + int hbar = getHorizontalBarHeight(); + int vbar = getVerticalBarWidth(); + int cx = getContentsX(), cy = getContentsY(); + int right = getContentsX() + getVisibleWidth() - vbar; + int bottom = getContentsY() + getVisibleHeight() - hbar; + boolean align_h = false, align_v = false; + + if (_x < getContentsX()) { + cx = _x; + } else if (_x + _w > right) { + cx = _x - _w; + } + if (_y < getContentsY()) { + cy = _y; + + } else if (_y + _h > bottom) { + cy = _y - _h; + } + + if (_w > getVisibleWidth()) + align_h = true; + if (_h > getVisibleHeight()) + align_v = true; + // compute alignment on visible area horizontally + if (align_h || (forceAlign && _x + _w > right)) { + // use _align flags + if ((_align & SWT.LEFT) != 0) { + cx = _x; + } else if ((_align & SWT.RIGHT) != 0) { + cx = right - _w; + } else // center + { + cx = _x + (_w - getVisibleWidth()) / 2; + } + } + // compute alignment on visible area vertically + if (align_v || (forceAlign && _y + _h > bottom)) { + // use _align flags + if ((_align & SWT.TOP) != 0) { + cy = _y; + } else if ((_align & SWT.BOTTOM) != 0) { + cy = bottom - _h; + } else // center + { + cy = _y + (_h - getVisibleHeight()) / 2; + } + } + setContentsPos(cx, cy); + } + + /** + * @return true if point is visible (expressed in contents coordinates) + */ + public boolean isVisible(int _px, int _py) { + if (_px < getContentsX()) + return false; + if (_py < getContentsY()) + return false; + if (_px > (getContentsX() + getVisibleWidth())) + return false; + if (_py > (getContentsY() + getVisibleHeight())) + return false; + return true; + } + + /** + * @return true if rectangle if partially visible. + */ + public boolean isVisible(int _x, int _y, int _w, int _h) { + if (_x + _w < getContentsX()) + return false; + if (_y + _h < getContentsY()) + return false; + int vr = getContentsX() + getVisibleWidth(); + int vb = getContentsY() + getVisibleHeight(); + if (_x > vr) + return false; + if (_y > vb) + return false; + return true; + } + + /** + * @return visible part of rectangle, or null if rectangle is not visible. rectangle is expressed in contents + * coordinates. + */ + public Rectangle getVisiblePart(int _x, int _y, int _w, int _h) { + if (_x + _w < getContentsX()) + return null; + if (_y + _h < getContentsY()) + return null; + int vr = getContentsX() + getVisibleWidth(); + int vb = getContentsY() + getVisibleHeight(); + if (_x > vr) + return null; + if (_y > vb) + return null; + int rr = _x + _w, rb = _y + _h; + int nl = Math.max(_x, getContentsX()), nt = Math.max(_y, getContentsY()), nr = Math.min(rr, vr), nb = Math.min(rb, vb); + Rectangle c = new Rectangle(nl, nt, nr - nl, nb - nt); + return c; + } + + public final Rectangle getVisiblePart(Rectangle _r) { + if (_r == null) + return null; + return getVisiblePart(_r.x, _r.y, _r.width, _r.height); + } + + /** + * Change top left position of visible area. Check if the given point is inside contents area. + * + * @param _x + * @param _y + * @return true if view really moves + */ + public boolean setContentsPos(int _x, int _y) { + int nx = _x, ny = _y; + if (getVisibleWidth() >= getContentsWidth()) { + nx = 0; + } else { + if (_x < 0) { + nx = 0; + } else if (_x + getVisibleWidth() > getContentsWidth()) { + nx = getContentsWidth() - getVisibleWidth(); + } + } + if (getVisibleHeight() >= getContentsHeight()) { + ny = 0; + } else { + if (_y <= 0) { + ny = 0; + } else if (_y + getVisibleHeight() > getContentsHeight()) { + ny = getContentsHeight() - getVisibleHeight(); + } + } + // no move + if (nx == contents_x_ && ny == contents_y_) { + return false; + } + contents_x_ = nx; + contents_y_ = ny; + updateScrollBarsValues(); + // ? find smallest area to redraw only them ? + viewcontrol_.redraw(); + return true; + } + + // redefined to return our vertical bar + @Override + public ScrollBar getVerticalBar() { + return vertsb_.getVerticalBar(); + } + + // redefined to return out horizontal bar + @Override + public ScrollBar getHorizontalBar() { + return horzsb_.getHorizontalBar(); + } + + public static final int VBAR = 0x01; + public static final int HBAR = 0x02; + + /** + * compute visibility of vert/hor bar using given width/height and current visibility (i.e. is bar size are already in + * for_xxx) + */ + public int computeBarVisibility(int for_width, int for_height, boolean curr_h_vis, boolean curr_v_vis) { + int vis = 0x00; + switch (v_scrollbar_mode_) { + case ALWAYS_OFF: + break; + case ALWAYS_ON: + vis |= VBAR; + break; + case AUTO: + if (getContentsHeight() > for_height) { + vis = VBAR; + // v bar size is already in for_width. + if (!curr_v_vis)// (curr_vis&0x01)==0) + { + for_width -= getVerticalBarWidth(); + } + } + break; + } + switch (h_scrollbar_mode_) { + case ALWAYS_OFF: + break; + case ALWAYS_ON: + vis |= HBAR; + break; + case AUTO: + if (getContentsWidth() > for_width) { + vis |= HBAR; + // h bar is not in for_height + if (!curr_h_vis)// (curr_vis&0x02)==0 ) + { + if (getContentsHeight() > for_height - getHorizontalBarHeight()) { + vis |= VBAR; + } + } + } + break; + } + return vis; + } + + /** + * setup scroll bars visibility, return true if one of visibility changed. + */ + protected boolean updateScrollBarVisiblity() { + boolean change = false; + + boolean curr_v_vis = vertsb_.getVisible(); + boolean curr_h_vis = horzsb_.getVisible(); + int bar_new_vis = computeBarVisibility(getVisibleWidth(), getVisibleHeight(), curr_h_vis, curr_v_vis); + boolean new_v_vis = (bar_new_vis & VBAR) != 0; + boolean new_h_vis = (bar_new_vis & HBAR) != 0; + // System.out.println("SV--updateScrollBarVis old, h:"+curr_h_vis+" v:"+curr_v_vis+" new="+bar_new_vis); + if (curr_v_vis ^ new_v_vis) // vertsb_.getVisible() ) + { + vertsb_.setVisible(new_v_vis); + change = true; + } + if (curr_h_vis ^ new_h_vis) { + horzsb_.setVisible(new_h_vis); + change = true; + } + + // update corner control visibility: + if (corner_control_ != null && change) { + boolean vis = new_v_vis || new_h_vis; + if (vis ^ corner_control_.getVisible()) { + corner_control_.setVisible(vis); + change = true; // but must be already the case + } + } + return change; + } + + /** + * Setup scroll bar using contents, visible and scroll bar mode properties. + * + */ + protected void updateScrollBarsValues() { + // System.out.println("UPDATE-SCROLL-BAR-VALUES"); + /* update vertical scrollbar */ + ScrollBar b = getVerticalBar(); + if (b != null) { + b.setMinimum(0); + b.setMaximum(getContentsHeight()); + b.setThumb(getVisibleHeight()); + b.setPageIncrement(getVisibleHeight()); + b.setIncrement(v_scrollbar_increment_); + b.setSelection(getContentsY()); + } + + // update "hidden" vertical bar too + b = viewcontrol_.getVerticalBar(); + if (b != null) { + b.setMinimum(0); + b.setMaximum(getContentsHeight()); + b.setThumb(getVisibleHeight()); + b.setPageIncrement(getVisibleHeight()); + b.setIncrement(v_scrollbar_increment_); + b.setSelection(getContentsY()); + } + + /* update horizontal scrollbar */ + b = getHorizontalBar(); + if (b != null) { + b.setMinimum(0); + b.setMaximum(getContentsWidth()); + b.setThumb(getVisibleWidth()); + b.setSelection(getContentsX()); + b.setPageIncrement(getVisibleWidth()); + b.setIncrement(h_scrollbar_increment_); + } + // update "hidden" horizontal bar too + b = viewcontrol_.getHorizontalBar(); + if (b != null) { + b.setMinimum(0); + b.setMaximum(getContentsWidth()); + b.setThumb(getVisibleWidth()); + b.setSelection(getContentsX()); + b.setPageIncrement(getVisibleWidth()); + b.setIncrement(h_scrollbar_increment_); + } + } + + /** + * Change the control used in the bottom right corner (between two scrollbar), if control is null reset previous + * corner control. This control is visible only if at least one scrollbar is visible. Given control will be disposed + * by ScrollView, at dispose() time, at next setCornetControl() call or when calling setOverviewEnabled(). Pay + * attention calling this reset overview feature until setOverviewEnabled(true) if called. + */ + public void setCornerControl(Control _w) { + if (corner_control_ != null) { + corner_control_.dispose(); + } + corner_control_ = _w; + if (corner_control_ != null) { + ScrollBar vb = getVerticalBar(); + ScrollBar hb = getHorizontalBar(); + boolean vis = vb.getVisible() || hb.getVisible(); + corner_control_.setVisible(vis); + } + } + + /** + * Transform (x,y) point in widget coordinates to contents coordinates. + * + * @param x + * @param y + * @return org.eclipse.swt.graphics.Point + */ + public final Point viewToContents(int _x, int _y) { + Point p = new Point(viewToContentsX(_x), viewToContentsY(_y)); + return p; + } + + /** Transform x in widget coordinates to contents coordinates */ + public int viewToContentsX(int _x) { + return contents_x_ + _x; + } + + /** Transform y in widget coordinates to contents coordinates */ + public int viewToContentsY(int _y) { + return contents_y_ + _y; + } + + /** + * Transform (x,y) point from contents coordinates, to widget coordinates. + * + * @param x + * @param y + * @return org.eclipse.swt.graphics.Point + */ + public final Point contentsToView(int _x, int _y) { + Point p = new Point(contentsToViewX(_x), contentsToViewY(_y)); + return p; + } + + /** + * Transform X axis coordinates from contents to widgets. + * + * @param _x contents coordinate to transform + * @return coordinate in widget area + */ + public int contentsToViewX(int _x) { + return _x - contents_x_; + } + + /** + * Transform Y axis coordinates from contents to widgets. + * + * @param _y contents coordinate to transform + * @return coordinate in widget area + */ + public int contentsToViewY(int _y) { + return _y - contents_y_; + } + + /** + * @return int the visible height of scroll view, might be > contentsHeight() + */ + public int getVisibleHeight() { + Rectangle r = viewcontrol_.getClientArea(); + return r.height; + } + + /** + * @return int the visible width of scroll view, might be > contentsWidth() + */ + public int getVisibleWidth() { + Rectangle r = viewcontrol_.getClientArea(); + return r.width; + } + + /** + * Add support for arrow key, scroll the ... scroll view. But you can redefine this method for your convenience. + */ + protected void keyPressedEvent(KeyEvent _e) { + switch (_e.keyCode) { + case SWT.ARROW_UP: + scrollBy(0, -getVisibleHeight()); + break; + case SWT.ARROW_DOWN: + scrollBy(0, +getVisibleHeight()); + break; + case SWT.ARROW_LEFT: + scrollBy(-getVisibleWidth(), 0); + break; + case SWT.ARROW_RIGHT: + scrollBy(+getVisibleWidth(), 0); + break; + } + } + + /** Redefine this method at your convenience */ + protected void keyReleasedEvent(KeyEvent _e) { + } + + /** + * Called when ScrollView view is resized. + * + * @param _event + */ + protected void viewResized() { + // System.out.println("SV--viewResizeEvent()"); + // scroll contents x,y if visible area bigger than contents... + if (!setContentsPos(getContentsX(), getContentsY())) { + // if no scroll done, scroll bar must be updated. + /* + * if( ocx==getContentsX() && ocy==getContentsY() ) { updateScrollBars(); } + */ + } + } + + /** @return vertical bar width, even if bar isn't visible */ + public int getVerticalBarWidth() { + // include vertical bar width and trimming of scrollable used + int bw = vertsb_.computeTrim(0, 0, 0, 0).width; + return bw + 1; + } + + /** @return horizontal bar height even if bar isn't visible */ + public int getHorizontalBarHeight() { + // include horiz. bar height and trimming of scrollable used + int bh = horzsb_.computeTrim(0, 0, 0, 0).height; + // +1 because win32 H.bar need 1 pixel canvas size to appear ! (strange no ?) + return bh + 1; + } + + @Override + public Rectangle computeTrim(int x, int y, int w, int h) { + Rectangle r = new Rectangle(x, y, w, h); + int bar_vis = computeBarVisibility(w, h, false, false); + if ((bar_vis & VBAR) != 0) { + r.width += getVerticalBarWidth(); + } + if ((bar_vis & HBAR) != 0) { + r.height += getHorizontalBarHeight(); + } + return r; + } + + /** Internal layout for ScrollView, handle scrollbars, drawzone and corner control */ + protected class SVLayout extends Layout { + @Override + protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { + Point p = new Point(250, 250); + if (contents_width_ < p.x) + p.x = contents_width_; + if (contents_height_ < p.y) + p.y = contents_height_; + return p; + } + + int seek = 0; + boolean dont_layout = false; + + @Override + protected void layout(Composite composite, boolean flushCache) { + if (dont_layout) + return; + seek++; + if (seek > 10) + dont_layout = true; + + // System.out.println(">>> SV.layout() {"); + Point cs = composite.getSize(); + int bar_vis = computeBarVisibility(cs.x, cs.y, false, false); + boolean vb_vis = (bar_vis & VBAR) != 0; + boolean hb_vis = (bar_vis & HBAR) != 0; + vertsb_.setVisible(vb_vis); + horzsb_.setVisible(hb_vis); + int vbw = getVerticalBarWidth(); + int hbh = getHorizontalBarHeight(); + int wb = vb_vis ? vbw : 0; + int hb = hb_vis ? hbh : 0; + int cww = 0, cwh = 0; + // System.out.println("SV-LAYOUT H.vis="+hb_vis+" V.vis="+vb_vis); + + if (corner_control_ != null && (vb_vis || hb_vis))// corner_control_.getVisible()) + { + corner_control_.setVisible(true); + cww = vbw; + cwh = hbh; + if (wb == 0) + wb = vbw; + if (hb == 0) + hb = hbh; + } else if (vb_vis && hb_vis) { + if (corner_control_ != null) + corner_control_.setVisible(false); + cww = vbw; + cwh = hbh; + } + if (vb_vis || hb_vis) + updateScrollBarsValues(); + + int vw = cs.x - (vb_vis ? vbw : 0); + int vh = cs.y - (hb_vis ? hbh : 0); + int vbx = cs.x - wb; + int hby = cs.y - hb; + Rectangle rc = viewcontrol_.getClientArea(); + int old_width = rc.width; + int old_height = rc.height; + // provoque pas un viewResize ??? + viewcontrol_.setBounds(0, 0, vw, vh); + boolean do_view_resize = false; + rc = viewcontrol_.getClientArea(); + if (old_width != rc.width || old_height != rc.height) { + // area size change, so visibleWidth()/Height() change too + // so scrollbars visibility ma change too.. + // so need an other layout ! + /* + * if( updateScrollBarVisiblity() ) { layout( composite, flushCache); + * System.out.println("<<< SV.layout() } (recursive)"); return ; } + */ + do_view_resize = true; + } + if (vb_vis) { + vertsb_.setBounds(vbx, 0, wb, cs.y - cwh); + } + if (hb_vis) { + horzsb_.setBounds(0, hby, cs.x - cww, hb); + } + if (corner_control_ != null && corner_control_.getVisible()) { + corner_control_.setBounds(vbx, hby, vbw, hbh); + } + updateScrollBarsValues(); + if (do_view_resize) { + // System.out.println(" -layout do_view_resize old="+old_width+"x"+old_height+" new="+viewcontrol_.getClientArea()); + viewResized(); + } + // System.out.println("<<< SV.layout() }"); + seek--; + if (seek == 0) + dont_layout = false; + } + } + + // static must take place here... cursor is created once. + protected static Cursor overview_cursor_; + + /** Support for click-and-see overview shell on this ScrollView */ + protected class Overview { + // factors from real and overview sizes, for mouse move speed. + protected float overview_factor_x_, overview_factor_y_; + // shell use to show overview + protected Shell overview; + // save mouse cursor location for disappear(); + protected int save_cursor_x, save_cursor_y; + + /** apply overview support on a control. Replace existing corner_widget */ + public void useControl(Control _c) { + final Point pos = _c.getLocation(); + _c.addMouseListener(new MouseListener() { + @Override + public void mouseDoubleClick(MouseEvent e) { + } + + @Override + public void mouseDown(MouseEvent e) { + overviewAppear(e.x, e.y); + } + + @Override + public void mouseUp(MouseEvent e) { + overviewDisappear(); + } + }); + + _c.addFocusListener(new FocusListener() { + + @Override + public void focusGained(FocusEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void focusLost(FocusEvent e) { + if (overviewing()) + overviewDisappear(false); + } + + }); + _c.addKeyListener(new KeyListener() { + + @Override + public void keyPressed(KeyEvent e) { + if (e.keyCode == 32 && !overviewing()) { + overviewAppear(pos.x, pos.y); + } else if (e.keyCode == 32) { + overviewDisappear(); + } + if (e.keyCode == SWT.ARROW_DOWN) { + overviewMove(0, 1, e); + } + + if (e.keyCode == SWT.ARROW_UP) { + overviewMove(0, -1, e); + } + + if (e.keyCode == SWT.ARROW_RIGHT) { + overviewMove(1, 0, e); + } + + if (e.keyCode == SWT.ARROW_LEFT) { + overviewMove(-1, 0, e); + } + } + + @Override + public void keyReleased(KeyEvent e) { + } + }); + _c.addMouseMoveListener(new MouseMoveListener() { + private int refReshCount = 0; + @Override + public void mouseMove(MouseEvent e) { + if (overviewing()) { + // Slow down the refresh + if (refReshCount % 4 == 0) { + overviewMove(e); + } + refReshCount++; + } + } + }); + } + + /** Dispose controls of overview */ + public void dispose() { + if (overview != null) + overview.dispose(); + } + + /** @return true if overview is currently on screen */ + protected boolean overviewing() { + return (overview != null && overview.isVisible()); + } + + /** Process overview appear */ + protected void overviewAppear(int mx, int my) { + if (overview == null) { + overview = new Shell(getShell(), SWT.ON_TOP | SWT.NO_BACKGROUND); + overview.addPaintListener(new PaintListener() { + @Override + public void paintControl(PaintEvent e) { + drawOverview(e.gc, overview.getClientArea()); + } + }); + } + // always the same.. + // overview.setBackground( viewcontrol_.getBackground() ); + overview.setForeground(viewcontrol_.getForeground()); + + // get location of shell (in screeen coordinates) + Point p = toGlobalCoordinates(corner_control_, 0, 0); + int x = p.x; + int y = p.y; + int w, h; + w = h = overview_size_; + Rectangle scr = getDisplay().getBounds(); + Point ccs = corner_control_.getSize(); + try { + if (contents_width_ > contents_height_) { + float ratio = contents_height_ / (float) contents_width_; + h = (int) (w * ratio); + if (h < ccs.y) + h = ccs.y; + else if (h >= scr.height / 2) + h = scr.height / 2; + } else { + float ratio = contents_width_ / (float) contents_height_; + w = (int) (h * ratio); + if (w < ccs.x) + w = ccs.x; + else if (w >= scr.width / 2) + w = scr.width / 2; + } + overview_factor_x_ = contents_width_ / (float) w; + overview_factor_y_ = contents_height_ / (float) h; + } + // no contents size set ? + catch (java.lang.ArithmeticException e) { + } + + // try pop-up on button, extending to bottom right, + // if outside screen, extend pop-up to top left + // if( x+w > scr.width ) x = scr.width-w; //x += corner_control_.getSize().x-w; + // if( y+h > scr.height ) y = scr.height-h;//y += corner_control_.getSize().y-h; + if (x <= 0) + x = 1; + if (y <= 0) + y = 1; + x = x - w + ccs.x; + y = y - h + ccs.y; + overview.setBounds(x, y, w, h); + overview.setVisible(true); + overview.redraw(); + // mouse cursor disappear, so set invisible mouse cursor ... + // if (hideCursor) + { + if (overview_cursor_ == null) { + RGB rgb[] = { new RGB(0, 0, 0), new RGB(255, 0, 0) }; + PaletteData pal_ = new PaletteData(rgb); + int s = 1; + byte src[] = new byte[s * s]; + byte msk[] = new byte[s * s]; + for (int i = 0; i < s * s; ++i) + src[i] = (byte) 0xFF; + ImageData i_src = new ImageData(s, s, 1, pal_, 1, src); + ImageData i_msk = new ImageData(s, s, 1, pal_, 1, msk); + overview_cursor_ = new Cursor(null, i_src, i_msk, 0, 0); + } + corner_control_.setCursor(overview_cursor_); + } + // convert to global coordinates + p = toGlobalCoordinates(corner_control_, mx, my); + save_cursor_x = p.x; + save_cursor_y = p.y; + + Rectangle r = overview.getClientArea(); + int cx = (int) (r.width * contents_x_ / (float) contents_width_); + int cy = (int) (r.height * contents_y_ / (float) contents_height_); + + // cx,cy to display's global coordinates + p = toGlobalCoordinates(overview.getParent(), cx, cy); + cx = p.x; + cy = p.y; + + } + + /** Process disappear of overview */ + protected void overviewDisappear() { + overviewDisappear(true); + } + + /** Process disappear of overview */ + protected void overviewDisappear(boolean restoreCursorLoc) { + if (overview == null) + return; + overview.setVisible(false); + corner_control_.setCursor(null); + if (restoreCursorLoc) + getDisplay().setCursorLocation(save_cursor_x, save_cursor_y); + overview.dispose(); + overview = null; + } + + protected void overviewMove(MouseEvent event) { + Point p = toGlobalCoordinates(corner_control_, event.x, event.y); + int dx = p.x - save_cursor_x; + int dy = p.y - save_cursor_y; + overviewMove(dx, dy, event); + } + + /** Process mouse move event when overviewing */ + protected void overviewMove(int dx, int dy, TypedEvent event) { + boolean ctrl = false; + boolean shift = false; + + if (event instanceof MouseEvent) { + MouseEvent e = (MouseEvent) event; + getDisplay().setCursorLocation(save_cursor_x, save_cursor_y); + ctrl = (e.stateMask & SWT.CONTROL) != 0; + shift = (e.stateMask & SWT.SHIFT) != 0; + } else if (event instanceof KeyEvent) { + KeyEvent e = (KeyEvent) event; + ctrl = (e.stateMask & SWT.CONTROL) != 0; + shift = (e.stateMask & SWT.SHIFT) != 0; + } + + int cx = contents_x_; + int cy = contents_y_; + float fx = overview_factor_x_; + float fy = overview_factor_y_; + + if (ctrl && shift) { + if ((fx * 0.25f > 1) && (fy * 0.25 > 1)) { + fx = fy = 1.0f; + } else { + fx *= 0.1f; + fy *= 0.1f; + } + } else if (ctrl) { + fx *= 0.5f; + fy *= 0.5f; + } else if (shift) { + fx *= 0.5f; + fy *= 0.5f; + } + scrollBy((int) (fx * dx), (int) (fy * dy)); + if (cx != contents_x_ || cy != contents_y_) { + overview.redraw(); + overview.update(); // draw now ! + } + } + + protected Point toGlobalCoordinates(Control _loc, int _x, int _y) { + Point p = new Point(_x, _y); + for (Control c = _loc; c != null; c = c.getParent()) { + // control might have client area with 'decorations' + int trim_x = 0, trim_y = 0; + // other kind of widget with trimming ?? + if (c instanceof Scrollable) { + Scrollable s = (Scrollable) c; + Rectangle rr = s.getClientArea(); + Rectangle tr = s.computeTrim(rr.x, rr.y, rr.width, rr.height); + trim_x = rr.x - tr.x; + trim_y = rr.y - tr.y; + } + p.x += c.getLocation().x + trim_x; + p.y += c.getLocation().y + trim_y; + } + return p; + } + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/TimeCompressionBar.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/TimeCompressionBar.java new file mode 100755 index 0000000000..b6c709f711 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/TimeCompressionBar.java @@ -0,0 +1,920 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: TimeCompressionBar.java,v 1.3 2008/01/24 02:29:01 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + * + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.AsyncMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.AsyncMessageReturn; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.BaseMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.ExecutionOccurrence; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Frame; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.ITimeRange; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Metrics; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SDTimeEvent; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SyncMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IColor; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.impl.ColorImpl; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.preferences.SDViewPref; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.TimeEventComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.ACC; +import org.eclipse.swt.accessibility.Accessible; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleControlAdapter; +import org.eclipse.swt.accessibility.AccessibleControlEvent; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; + +/** + * @author sveyrier + * + */ +public class TimeCompressionBar extends ScrollView implements DisposeListener { + + /** + * The listener list + */ + protected ArrayList listenerList = null; + + protected Frame frame = null; + protected List nodeList = null; + protected TmfTimestamp min = new TmfTimestamp(); + protected TmfTimestamp max = new TmfTimestamp(); + protected float zoomValue = 1; + protected DrawableToolTip tooltip = null; + + protected ColorImpl[] col; + + protected Accessible accessible = null; + + protected int focusedWidget = -1; + + protected SDView view = null; + + protected Lifeline ll = null; + protected int ls = 0; + protected int ln = 0; + protected IColor lc = null; + + protected int nextNodeY = 0; + protected int prevNodeY = 0; + + public TimeCompressionBar(Composite parent, int s) { + super(parent, s | SWT.NO_BACKGROUND, false); + setVScrollBarMode(ScrollView.ALWAYS_OFF); + setHScrollBarMode(ScrollView.ALWAYS_OFF); + listenerList = new ArrayList(); + col = new ColorImpl[10]; + col[0] = new ColorImpl(Display.getDefault(), 255, 229, 229); + col[1] = new ColorImpl(Display.getDefault(), 255, 204, 204); + col[2] = new ColorImpl(Display.getDefault(), 255, 178, 178); + col[3] = new ColorImpl(Display.getDefault(), 255, 153, 153); + col[4] = new ColorImpl(Display.getDefault(), 255, 127, 127); + col[5] = new ColorImpl(Display.getDefault(), 255, 102, 102); + col[6] = new ColorImpl(Display.getDefault(), 255, 76, 76); + col[7] = new ColorImpl(Display.getDefault(), 255, 51, 51); + col[8] = new ColorImpl(Display.getDefault(), 255, 25, 25); + col[9] = new ColorImpl(Display.getDefault(), 255, 0, 0); + super.addDisposeListener(this); + + accessible = getViewControl().getAccessible(); + + accessible.addAccessibleListener(new AccessibleAdapter() { + @Override + public void getName(AccessibleEvent e) { + if (e.childID == ACC.CHILDID_SELF) { + // e.result = "Sequence Diagram"; + } + // Case toolTip + else if (e.childID == 0) { + if (tooltip != null) + e.result = tooltip.getAccessibleText(); + } else if (e.childID == 1) { + createFakeTooltip(); + e.result = tooltip.getAccessibleText(); + } + } + }); + + accessible.addAccessibleControlListener(new AccessibleControlAdapter() { + @Override + public void getFocus(AccessibleControlEvent e) { + if (focusedWidget == -1) + e.childID = ACC.CHILDID_SELF; + else + e.childID = focusedWidget; + } + + @Override + public void getRole(AccessibleControlEvent e) { + switch (e.childID) { + case ACC.CHILDID_SELF: + e.detail = ACC.ROLE_CLIENT_AREA; + break; + case 0: + e.detail = ACC.ROLE_TOOLTIP; + break; + case 1: + e.detail = ACC.ROLE_LABEL; + break; + } + } + + @Override + public void getState(AccessibleControlEvent e) { + e.detail = ACC.STATE_FOCUSABLE; + if (e.childID == ACC.CHILDID_SELF) { + e.detail |= ACC.STATE_FOCUSED; + } else { + e.detail |= ACC.STATE_SELECTABLE; + if (e.childID == focusedWidget) + e.detail |= ACC.STATE_FOCUSED | ACC.STATE_SELECTED | ACC.STATE_CHECKED; + } + } + }); + + getViewControl().addTraverseListener(new TraverseListener() { + + @Override + public void keyTraversed(TraverseEvent e) { + if ((e.detail == SWT.TRAVERSE_TAB_NEXT) || (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)) + e.doit = true; + + } + + }); + + addTraverseListener(new TraverseListener() { + + @Override + public void keyTraversed(TraverseEvent e) { + if ((e.detail == SWT.TRAVERSE_TAB_NEXT) || (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)) + e.doit = true; + + } + + }); + + getViewControl().addFocusListener(new FocusListener() { + + @Override + public void focusGained(FocusEvent e) { + + redraw(); + } + + @Override + public void focusLost(FocusEvent e) { + redraw(); + } + }); + } + + void setFocus(int newFocusShape) { + focusedWidget = newFocusShape; + if (focusedWidget == -1) { + getViewControl().getAccessible().setFocus(ACC.CHILDID_SELF); + } else { + getViewControl().getAccessible().setFocus(focusedWidget); + } + } + + public void setFrame(Frame theFrame) { + frame = theFrame; + min = frame.getMinTime(); + max = frame.getMaxTime(); + } + + @Override + protected void drawContents(GC gc, int clipx, int clipy, int clipw, int cliph) { + if (frame == null) + return; + nodeList = new ArrayList(); + int messageArraysStep = 1; + + if ((Metrics.getMessageFontHeigth() + Metrics.MESSAGES_NAME_SPACING * 2) * zoomValue < Metrics.MESSAGE_SIGNIFICANT_VSPACING + 1) + messageArraysStep = Math.round(Metrics.MESSAGE_SIGNIFICANT_VSPACING + 1 / ((Metrics.getMessageFontHeigth() + Metrics.MESSAGES_NAME_SPACING * 2) * zoomValue)); + + int firstVisible = frame.getFirstVisibleSyncMessage(); + if (firstVisible > 0) + firstVisible = firstVisible - 1; + for (int i = firstVisible; i < frame.syncMessageCount(); i = i + messageArraysStep) { + SyncMessage m = frame.getSyncMessage(i); + if (m.hasTimeInfo()) { + SDTimeEvent t = new SDTimeEvent(m.getStartTime(), m.getEventOccurrence(), (ITimeRange) m); + nodeList.add(t); + if (m.getY() * zoomValue > getContentsY() + getVisibleHeight()) + break; + } + } + + firstVisible = frame.getFirstVisibleSyncMessageReturn(); + if (firstVisible > 0) + firstVisible = firstVisible - 1; + for (int i = firstVisible; i < frame.syncMessageReturnCount(); i = i + messageArraysStep) { + SyncMessage m = frame.getSyncMessageReturn(i); + if (m.hasTimeInfo()) { + SDTimeEvent t = new SDTimeEvent(m.getStartTime(), m.getEventOccurrence(), (ITimeRange) m); + nodeList.add(t); + if (m.getY() * zoomValue > getContentsY() + getVisibleHeight()) + break; + } + } + + firstVisible = frame.getFirstVisibleAsyncMessage(); + if (firstVisible > 0) + firstVisible = firstVisible - 1; + for (int i = firstVisible; i < frame.asyncMessageCount(); i = i + messageArraysStep) { + AsyncMessage m = frame.getAsyncMessage(i); + if (m.hasTimeInfo()) { + SDTimeEvent t = new SDTimeEvent(m.getStartTime(), m.getStartOccurrence(), (ITimeRange) m); + nodeList.add(t); + t = new SDTimeEvent(m.getEndTime(), m.getEndOccurrence(), (ITimeRange) m); + nodeList.add(t); + if (m.getY() * zoomValue > getContentsY() + getVisibleHeight()) + break; + } + } + + firstVisible = frame.getFirstVisibleAsyncMessageReturn(); + if (firstVisible > 0) + firstVisible = firstVisible - 1; + for (int i = firstVisible; i < frame.asyncMessageReturnCount(); i = i + messageArraysStep) { + AsyncMessageReturn m = frame.getAsyncMessageReturn(i); + if (m.hasTimeInfo()) { + SDTimeEvent t = new SDTimeEvent(m.getStartTime(), m.getStartOccurrence(), (ITimeRange) m); + nodeList.add(t); + t = new SDTimeEvent(m.getEndTime(), m.getEndOccurrence(), (ITimeRange) m); + nodeList.add(t); + if (m.getY() * zoomValue > getContentsY() + getVisibleHeight()) + break; + } + } + + /* + * for (int i=0; igetContentsY()+getVisibleHeight()) break; + * } + * float g= lifeline.getX()*zoomValue; + * int h=frame.getContentX(); int r=frame.getVisibleWidth(); + * } + */ + List executionOccurrencesWithTime = frame.getExecutionOccurrencesWithTime(); + if (executionOccurrencesWithTime != null) { + nodeList.addAll(executionOccurrencesWithTime); + } + + SDTimeEvent[] temp = nodeList.toArray(new SDTimeEvent[0]); + Arrays.sort(temp, new TimeEventComparator()); + nodeList = Arrays.asList(temp); + + Image dbuffer = null; + GC gcim = null; + try { + dbuffer = new Image(getDisplay(), getClientArea().width, getClientArea().height); + } catch (Exception e) { + System.out.println(e.toString()); + } + gcim = new GC(dbuffer); + for (int i = 0; i < nodeList.size() - 1; i++) { + SDTimeEvent m1 = (SDTimeEvent) nodeList.get(i); + SDTimeEvent m2 = (SDTimeEvent) nodeList.get(i + 1); + + if (SDViewPref.getInstance().excludeExternalTime()) { + if ((m1.getGraphNode() instanceof BaseMessage) && (m2.getGraphNode() instanceof BaseMessage)) { + BaseMessage mes1 = (BaseMessage) m1.getGraphNode(); + BaseMessage mes2 = (BaseMessage) m2.getGraphNode(); + if ((mes2.getStartLifeline() == null) || (mes1.getEndLifeline() == null)) + continue; + } + } + + min = frame.getMinTime(); + max = frame.getMaxTime(); + TmfTimestamp minMaxdelta = max.getDelta(min); + double gr = (minMaxdelta.getValue()) / (double) 10; + + TmfTimestamp delta = m2.getTime().getDelta(m1.getTime()).getDelta(min); + long absDelta = Math.abs(delta.getValue()); + + ColorImpl color; + if (gr != 0) { + int colIndex = Math.round((float) (absDelta / gr)); + if (colIndex < col.length && colIndex > 0) + color = col[colIndex - 1]; + else if (colIndex <= 0) + color = col[0]; + else + color = col[col.length - 1]; + } else + color = col[0]; + + if (color.getColor() instanceof Color) + gcim.setBackground((Color) color.getColor()); + int y1 = ((GraphNode) m1.getGraphNode()).getY(); + int y2 = ((GraphNode) m2.getGraphNode()).getY(); + if (m1.getGraphNode() instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) m1.getGraphNode(); + if (as.getEndTime() == m1.getTime()) + y1 += as.getHeight(); + } + if (m2.getGraphNode() instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) m2.getGraphNode(); + if (as.getEndTime() == m2.getTime()) + y2 += as.getHeight(); + } + if (m1.getGraphNode() instanceof ExecutionOccurrence) { + + ExecutionOccurrence eo = (ExecutionOccurrence) m1.getGraphNode(); + if (m1.getEvent() == eo.getEndOccurrence()) + y1 += eo.getHeight(); + + if (m2.getGraphNode() instanceof ExecutionOccurrence) { + + ExecutionOccurrence eo2 = (ExecutionOccurrence) m2.getGraphNode(); + if (m2.getEvent() == eo2.getEndOccurrence()) + y2 += eo2.getHeight(); + + } + } + gcim.fillRectangle(contentsToViewX(0), contentsToViewY(Math.round(y1 * zoomValue)), 10, Math.round((y2 - y1) * zoomValue) + 1); + if (messageArraysStep == 1) { + Color backupColor = gcim.getForeground(); + gcim.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); + gcim.drawRectangle(contentsToViewX(0), contentsToViewY(Math.round(y1 * zoomValue)), 9, Math.round((y2 - y1) * zoomValue)); + gcim.setForeground(backupColor); + } + } + if (getViewControl().isFocusControl() || isFocusControl()) { + gcim.drawFocus(contentsToViewX(0), contentsToViewY(Math.round(prevNodeY * zoomValue)), contentsToViewX(10), Math.round((nextNodeY - prevNodeY) * zoomValue)); + } + try { + gc.drawImage(dbuffer, 0, 0, getClientArea().width, getClientArea().height, 0, 0, getClientArea().width, getClientArea().height); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + gcim.dispose(); + if (dbuffer != null) { + dbuffer.dispose(); + } + gc.dispose(); + } + + protected boolean checkFocusOnChilds(Control childs) { + if (childs instanceof Composite) { + Control[] child = ((Composite) childs).getChildren(); + for (int i = 0; i < child.length; i++) { + if (child[i].isFocusControl()) { + return true; + } else + checkFocusOnChilds(child[i]); + } + } + return false; + } + + @Override + public boolean isFocusControl() { + Control[] child = getChildren(); + for (int i = 0; i < child.length; i++) { + if (child[i].isFocusControl()) { + return true; + } else + checkFocusOnChilds(child[i]); + } + return false; + } + + @Override + protected void contentsMouseMoveEvent(MouseEvent event) { + if (tooltip != null) + tooltip.hideToolTip(); + super.contentsMouseMoveEvent(event); + if (!isFocusControl() || getViewControl().isFocusControl()) { + Control[] child = getParent().getChildren(); + for (int i = 0; i < child.length; i++) { + if (child[i].isFocusControl()) { + // getViewControl().setFocus(); + break; + } + } + } + setFocus(-1); + } + + @Override + protected void contentsMouseHover(MouseEvent e) { + if (tooltip == null) { + tooltip = new DrawableToolTip(this); + } + if (frame != null) { + setFocus(0); + for (int i = 0; i < nodeList.size() - 1; i++) { + SDTimeEvent m1 = (SDTimeEvent) nodeList.get(i); + SDTimeEvent m2 = (SDTimeEvent) nodeList.get(i + 1); + + if (SDViewPref.getInstance().excludeExternalTime()) { + if ((m1.getGraphNode() instanceof BaseMessage) && (m2.getGraphNode() instanceof BaseMessage)) { + BaseMessage mes1 = (BaseMessage) m1.getGraphNode(); + BaseMessage mes2 = (BaseMessage) m2.getGraphNode(); + if ((mes2.getStartLifeline() == null) || (mes1.getEndLifeline() == null)) + continue; + } + } + + int y1 = ((GraphNode) m1.getGraphNode()).getY(); + int y2 = ((GraphNode) m2.getGraphNode()).getY(); + + if (m1.getGraphNode() instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) m1.getGraphNode(); + if (as.getEndTime() == m1.getTime()) + y1 += as.getHeight(); + } + if (m2.getGraphNode() instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) m2.getGraphNode(); + if (as.getEndTime() == m2.getTime()) + y2 += as.getHeight(); + } + if (m1.getGraphNode() instanceof ExecutionOccurrence) { + ExecutionOccurrence eo = (ExecutionOccurrence) m1.getGraphNode(); + if (m1.getEvent() == eo.getEndOccurrence()) + y1 += eo.getHeight(); + + if (m2.getGraphNode() instanceof ExecutionOccurrence) { + + ExecutionOccurrence eo2 = (ExecutionOccurrence) m2.getGraphNode(); + if (m2.getEvent() == eo2.getEndOccurrence()) + y2 += eo2.getHeight(); + + } + } + int m1Y = Math.round(y1 * zoomValue); + int m2Y = Math.round(y2 * zoomValue); + if ((m1Y < e.y) && (m2Y >= e.y)) { + TmfTimestamp delta = m2.getTime().getDelta(m1.getTime()); + tooltip.showToolTip(delta, min, max); + } + } + } + setFocus(0); + } + + @Override + protected void contentsMouseExit(MouseEvent e) { + if (tooltip != null) + tooltip.hideToolTip(); + } + + @Override + protected void contentsMouseUpEvent(MouseEvent event) { + selectTimeDelta(event.y, 0); + setFocus(); + super.contentsMouseUpEvent(event); + } + + /** + * Force the time compression bar to highlight the event occurrences between the two given messages. The event + * occurrences are highlighted on the first message's end lifeline + * + * @param mes1 the first message + * @param mes2 + */ + public void highlightRegion(BaseMessage mes1, BaseMessage mes2) { + if (frame == null) + return; + if (!(mes1 instanceof ITimeRange)) + return; + if (!(mes2 instanceof ITimeRange)) + return; + ITimeRange t1 = (ITimeRange) mes1; + ITimeRange t2 = (ITimeRange) mes2; + + TmfTimestamp time1 = t1.getStartTime(); + TmfTimestamp time2 = t2.getStartTime(); + int event1 = mes1.getEventOccurrence(); + int event2 = mes2.getEventOccurrence(); + + if (mes1 instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) mes2; + time1 = as.getEndTime(); + event1 = as.getEndOccurrence(); + } + if (mes2 instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) mes2; + if (as.getEndOccurrence() > as.getStartOccurrence()) { + time1 = as.getEndTime(); + event1 = as.getEndOccurrence(); + } else { + time1 = as.getStartTime(); + event1 = as.getStartOccurrence(); + } + } + + if (event1 > event2) { + BaseMessage tempMes = mes2; + mes2 = mes1; + mes1 = tempMes; + + t1 = (ITimeRange) mes1; + t2 = (ITimeRange) mes2; + + time1 = t1.getStartTime(); + time2 = t2.getStartTime(); + event1 = mes1.getEventOccurrence(); + event2 = mes2.getEventOccurrence(); + + if (mes1 instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) mes2; + time1 = as.getEndTime(); + event1 = as.getEndOccurrence(); + } + if (mes2 instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) mes2; + if (as.getEndOccurrence() > as.getStartOccurrence()) { + time1 = as.getEndTime(); + event1 = as.getEndOccurrence(); + } else { + time1 = as.getStartTime(); + event1 = as.getStartOccurrence(); + } + } + } + + TmfTimestamp minMaxdelta = max.getDelta(min); + double gr = (minMaxdelta.getValue()) / (double) 10; + + TmfTimestamp delta = time2.getDelta(time1).getDelta(min); + long absDelta = Math.abs(delta.getValue()); + + int colIndex = 0; + if (gr != 0) { + colIndex = Math.round((float) (absDelta / gr)); + if (colIndex >= col.length) + colIndex = col.length - 1; + else if (colIndex < 0) + colIndex = 0; + } else + colIndex = 0; + for (int j = 0; j < listenerList.size(); j++) { + ITimeCompressionListener list = (ITimeCompressionListener) listenerList.get(j); + if (mes1.getEndLifeline() != null) { + list.deltaSelected(mes1.getEndLifeline(), event1, event2 - event1, col[colIndex]); + } else if (mes2.getStartLifeline() != null) { + list.deltaSelected(mes2.getStartLifeline(), event1, event2 - event1, col[colIndex]); + } else + list.deltaSelected(mes1.getStartLifeline(), event1, event2 - event1, col[colIndex]); + } + } + + /** + * Force the time compression bar to highlight the event occurrences between the two given messages. The event + * occurrences are highlighted on the first message's end lifeline + * + * @param mes1 the first message + * @param mes2 + */ + public void highlightRegionSync(final BaseMessage mes1, final BaseMessage mes2) { + getDisplay().syncExec(new Runnable() { + @Override + public void run() { + highlightRegion(mes1, mes2); + } + }); + } + + @Override + public void scrollBy(int x, int y) { + } + + public void setZoom(float value) { + zoomValue = value; + redraw(); + } + + public void addTimeCompressionListener(ITimeCompressionListener listener) { + if (!listenerList.contains(listener)) + listenerList.add(listener); + } + + public void removeSelectionChangedListener(ITimeCompressionListener listener) { + listenerList.remove(listener); + } + + @Override + public void widgetDisposed(DisposeEvent e) { + if (tooltip != null) + tooltip.dispose(); + super.removeDisposeListener(this); + for (int i = 0; i < col.length; i++) + col[i].dispose(); + } + + @Override + protected void keyPressedEvent(KeyEvent _e) { + if (tooltip != null) + tooltip.hideToolTip(); + if (!isFocusControl() || getViewControl().isFocusControl()) { + Control[] child = getParent().getChildren(); + for (int i = 0; i < child.length; i++) { + if (child[i].isFocusControl()) { + // getViewControl().setFocus(); + break; + } + } + } + setFocus(-1); + + boolean top = false; + if (nextNodeY == 0) + top = true; + if ((frame != null) && (nextNodeY == 0)) { + for (int i = 0; i < nodeList.size() - 1 && i < 1; i++) { + SDTimeEvent m1 = (SDTimeEvent) nodeList.get(i); + SDTimeEvent m2 = (SDTimeEvent) nodeList.get(i + 1); + if (SDViewPref.getInstance().excludeExternalTime()) { + if ((m1.getGraphNode() instanceof BaseMessage) && (m2.getGraphNode() instanceof BaseMessage)) { + BaseMessage mes1 = (BaseMessage) m1.getGraphNode(); + BaseMessage mes2 = (BaseMessage) m2.getGraphNode(); + if ((mes2.getStartLifeline() == null) || (mes1.getEndLifeline() == null)) + continue; + } + } + + int y1 = ((GraphNode) m1.getGraphNode()).getY(); + int y2 = ((GraphNode) m2.getGraphNode()).getY(); + if (m1.getGraphNode() instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) m1.getGraphNode(); + if (as.getEndTime() == m1.getTime()) + y1 += as.getHeight(); + } + if (m2.getGraphNode() instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) m2.getGraphNode(); + if (as.getEndTime() == m2.getTime()) + y2 += as.getHeight(); + } + if (m1.getGraphNode() instanceof ExecutionOccurrence) { + ExecutionOccurrence eo = (ExecutionOccurrence) m1.getGraphNode(); + if (m1.getEvent() == eo.getEndOccurrence()) + y1 += eo.getHeight(); + + if (m2.getGraphNode() instanceof ExecutionOccurrence) { + + ExecutionOccurrence eo2 = (ExecutionOccurrence) m2.getGraphNode(); + if (m2.getEvent() == eo2.getEndOccurrence()) + y2 += eo2.getHeight(); + + } + } + prevNodeY = Math.round(y1 * zoomValue); + nextNodeY = Math.round(y2 * zoomValue); + } + } + + if (ll != null) + for (int j = 0; j < listenerList.size(); j++) { + ITimeCompressionListener list = (ITimeCompressionListener) listenerList.get(j); + list.deltaSelected(ll, ls, ln, lc); + } + + if (_e.keyCode == SWT.ARROW_DOWN) { + if (!top) + selectTimeDelta(nextNodeY + 1, 1); + else + selectTimeDelta(prevNodeY + 1, 1); + setFocus(1); + } else if (_e.keyCode == SWT.ARROW_UP) { + selectTimeDelta(prevNodeY - 1, 2); + setFocus(1); + } else if (_e.keyCode == SWT.ARROW_RIGHT) { + selectTimeDelta(prevNodeY, 1); + setFocus(1); + } + super.keyPressedEvent(_e); + } + + @Override + protected void keyReleasedEvent(KeyEvent _e) { + super.keyReleasedEvent(_e); + } + + /** + * 0 no direction, 1 = down, 2 = up + * + * @param dy + * @param direction + */ + protected void selectTimeDelta(int dy, int direction) { + SDTimeEvent lastM1 = null; + SDTimeEvent lastM2 = null; + int lastY1 = 0; + int lastY2 = 0; + boolean done = false; + if (frame != null) { + for (int i = 0; i < nodeList.size() - 1; i++) { + SDTimeEvent m1 = (SDTimeEvent) nodeList.get(i); + SDTimeEvent m2 = (SDTimeEvent) nodeList.get(i + 1); + if (SDViewPref.getInstance().excludeExternalTime()) { + if ((m1.getGraphNode() instanceof BaseMessage) && (m2.getGraphNode() instanceof BaseMessage)) { + BaseMessage mes1 = (BaseMessage) m1.getGraphNode(); + BaseMessage mes2 = (BaseMessage) m2.getGraphNode(); + if ((mes2.getStartLifeline() == null) || (mes1.getEndLifeline() == null)) + continue; + } + } + + int y1 = ((GraphNode) m1.getGraphNode()).getY(); + int y2 = ((GraphNode) m2.getGraphNode()).getY(); + if (m1.getGraphNode() instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) m1.getGraphNode(); + if (as.getEndTime() == m1.getTime()) + y1 += as.getHeight(); + } + if (m2.getGraphNode() instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) m2.getGraphNode(); + if (as.getEndTime() == m2.getTime()) + y2 += as.getHeight(); + } + if (m1.getGraphNode() instanceof ExecutionOccurrence) { + ExecutionOccurrence eo = (ExecutionOccurrence) m1.getGraphNode(); + if (m1.getEvent() == eo.getEndOccurrence()) + y1 += eo.getHeight(); + + if (m2.getGraphNode() instanceof ExecutionOccurrence) { + + ExecutionOccurrence eo2 = (ExecutionOccurrence) m2.getGraphNode(); + if (m2.getEvent() == eo2.getEndOccurrence()) + y2 += eo2.getHeight(); + + } + } + int m1Y = Math.round(y1 * zoomValue); + int m2Y = Math.round(y2 * zoomValue); + + if ((m1Y < dy) && (m2Y > dy) || (!done && m2Y > dy && direction == 1 && lastM1 != null) || (!done && m1Y > dy && direction == 2 && lastM1 != null)) { + if (m1Y > dy && direction == 2) { + m1 = lastM1; + m2 = lastM2; + m1Y = lastY1; + m2Y = lastY2; + } + done = true; + prevNodeY = m1Y; + nextNodeY = m2Y; + TmfTimestamp minMaxdelta = max.getDelta(min); + double gr = (minMaxdelta.getValue()) / (double) 10; + + TmfTimestamp delta = m2.getTime().getDelta(m1.getTime()).getDelta(min); + long absDelta = Math.abs(delta.getValue()); + + int colIndex = 0; + if (gr != 0) { + colIndex = Math.round((float) (absDelta / gr)); + if (colIndex >= col.length) + colIndex = col.length - 1; + else if (colIndex < 0) + colIndex = 0; + } else + colIndex = 0; + if (m1.getGraphNode() instanceof BaseMessage) { + BaseMessage mes1 = (BaseMessage) m1.getGraphNode(); + if (mes1.getEndLifeline() != null) { + ll = mes1.getEndLifeline(); + ls = m1.getEvent(); + ln = m2.getEvent() - m1.getEvent(); + lc = col[colIndex]; + } else if (m2.getGraphNode() instanceof BaseMessage && ((BaseMessage) m2.getGraphNode()).getStartLifeline() != null) { + ll = ((BaseMessage) m2.getGraphNode()).getStartLifeline(); + ls = m1.getEvent(); + ln = m2.getEvent() - m1.getEvent(); + lc = col[colIndex]; + } else { + ll = mes1.getStartLifeline(); + ls = m1.getEvent(); + ln = m2.getEvent() - m1.getEvent(); + lc = col[colIndex]; + } + } else if (m1.getGraphNode() instanceof ExecutionOccurrence) { + if (m2.getGraphNode() instanceof ExecutionOccurrence) { + ExecutionOccurrence eo = (ExecutionOccurrence) m2.getGraphNode(); + ll = eo.getLifeline(); + ls = m1.getEvent(); + ln = m2.getEvent() - m1.getEvent(); + lc = col[colIndex]; + } else { + ExecutionOccurrence eo = (ExecutionOccurrence) m1.getGraphNode(); + ll = eo.getLifeline(); + ls = m1.getEvent(); + ln = m2.getEvent() - m1.getEvent(); + lc = col[colIndex]; + } + } + for (int j = 0; j < listenerList.size(); j++) { + ITimeCompressionListener list = (ITimeCompressionListener) listenerList.get(j); + list.deltaSelected(ll, ls, ln, lc); + } + break; + } else { + lastM1 = m1; + lastM2 = m2; + lastY1 = m1Y; + lastY2 = m2Y; + } + } + } + } + + protected void createFakeTooltip() { + if (tooltip == null) { + tooltip = new DrawableToolTip(this); + } + if (frame != null) { + setFocus(0); + for (int i = 0; i < nodeList.size() - 1; i++) { + SDTimeEvent m1 = (SDTimeEvent) nodeList.get(i); + SDTimeEvent m2 = (SDTimeEvent) nodeList.get(i + 1); + + if (SDViewPref.getInstance().excludeExternalTime()) { + if ((m1.getGraphNode() instanceof BaseMessage) && (m2.getGraphNode() instanceof BaseMessage)) { + BaseMessage mes1 = (BaseMessage) m1.getGraphNode(); + BaseMessage mes2 = (BaseMessage) m2.getGraphNode(); + if ((mes2.getStartLifeline() == null) || (mes1.getEndLifeline() == null)) + continue; + } + } + + int y1 = ((GraphNode) m1.getGraphNode()).getY(); + int y2 = ((GraphNode) m2.getGraphNode()).getY(); + + if (m1.getGraphNode() instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) m1.getGraphNode(); + if (as.getEndTime() == m1.getTime()) + y1 += as.getHeight(); + } + if (m2.getGraphNode() instanceof AsyncMessage) { + AsyncMessage as = (AsyncMessage) m2.getGraphNode(); + if (as.getEndTime() == m2.getTime()) + y2 += as.getHeight(); + } + if (m1.getGraphNode() instanceof ExecutionOccurrence) { + ExecutionOccurrence eo = (ExecutionOccurrence) m1.getGraphNode(); + if (m1.getEvent() == eo.getEndOccurrence()) + y1 += eo.getHeight(); + + if (m2.getGraphNode() instanceof ExecutionOccurrence) { + + ExecutionOccurrence eo2 = (ExecutionOccurrence) m2.getGraphNode(); + if (m2.getEvent() == eo2.getEndOccurrence()) + y2 += eo2.getHeight(); + + } + } + int m1Y = Math.round(y1 * zoomValue); + int m2Y = Math.round(y2 * zoomValue); + if ((m1Y < prevNodeY + 1) && (m2Y >= prevNodeY + 1)) { + TmfTimestamp delta = m2.getTime().getDelta(m1.getTime()); + tooltip.showToolTip(delta, min, max); + tooltip.hideToolTip(); + } + } + } + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/AsyncMessage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/AsyncMessage.java new file mode 100755 index 0000000000..1aaf3926e2 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/AsyncMessage.java @@ -0,0 +1,414 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: AsyncMessage.java,v 1.3 2008/01/24 02:28:49 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import java.util.Comparator; + +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SortAsyncForBackward; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SortAsyncMessageComparator; + +/** + * A AsyncMessage is a asynchronous message which appear at two different event occurrences on each lifeline ends (sender + * and receiver).
+ *
+ *
+ * Usage example: + * + *

+ * Frame frame;
+ * Lifeline lifeLine1;
+ * Lifeline lifeLine2;
+ * 
+ * AsyncMessage message = new AsyncMessage();
+ * // Create a new event occurrence on each lifeline
+ * lifeline1.getNewOccurrenceIndex();
+ * lifeline2.getNewOccurrenceIndex();
+ * // Set the message sender and receiver
+ * message.setStartLifeline(lifeLine1);
+ * message.setEndLifline(lifeline2);
+ * message.setName("Message label");
+ * // add the message to the frame
+ * frame.addMessage(message);
+ * 
+ * + * @see Lifeline Lifeline for more event occurence details + * @author sveyrier + * + */ +public class AsyncMessage extends BaseMessage implements ITimeRange { + + protected boolean hasTime = false; + /** + * The time when the message begin + */ + protected TmfTimestamp endTime = new TmfTimestamp(); + + /** + * The time when the message end + */ + protected TmfTimestamp startTime = new TmfTimestamp(); + + /** + * The associated message. + */ + protected AsyncMessageReturn messageReturn = null; + + public static final String ASYNC_MESS_TAG = "AsyncMessage"; //$NON-NLS-1$ + + public AsyncMessage() { + prefId = ISDPreferences.PREF_ASYNC_MESS; + } + + @Override + public int getX() { + int x = super.getX(true); + int activationWidth = Metrics.EXECUTION_OCCURRENCE_WIDTH / 2; + if ((startLifeline != null) && (endLifeline != null) && (startLifeline.getX() > endLifeline.getX())) { + activationWidth = -activationWidth; + } + + if (isMessageStartInActivation(startEventOccurrence)) { + x = x + activationWidth; + } + return x; + } + + @Override + public int getY() { + if ((startLifeline != null) && (endLifeline != null)) { + return endLifeline.getY() + endLifeline.getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * startEventOccurrence; + } + return super.getY(); + } + + @Override + public int getWidth() { + int width = super.getWidth(true); + int activationWidth = Metrics.EXECUTION_OCCURRENCE_WIDTH / 2; + if ((startLifeline != null) && (endLifeline != null) && (startLifeline.getX() > endLifeline.getX())) { + activationWidth = -activationWidth; + } + + if (isMessageStartInActivation(startEventOccurrence)) + width = width - activationWidth; + + if (isMessageEndInActivation(endEventOccurrence)) + width = width - activationWidth; + + return width; + } + + @Override + public int getHeight() { + if ((startLifeline != null) && (endLifeline != null)) { + return (endLifeline.getY() + endLifeline.getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * endEventOccurrence) - getY(); + } + return super.getHeight(); + } + + /** + * Set the message return associated with this message. + * + * @param message the message return to associate + */ + protected void setMessageReturn(AsyncMessageReturn message) { + messageReturn = message; + } + + /** + * Set the event occurrence attached to this message for its end lifeline + * + * @param occurrence the event occurrence to set + */ + public void setEndOccurrence(int occurrence) { + endEventOccurrence = occurrence; + if (getStartLifeline() == null) + startEventOccurrence = occurrence; + informFrame(getEndLifeline(), occurrence); + } + + protected void informFrame(Lifeline lifeLine, int occurrence) { + if ((lifeLine != null) && (lifeLine.getFrame() != null)) + if (lifeLine.getFrame().getMaxEventOccurrence() < occurrence) + lifeLine.getFrame().setMaxEventOccurrence(occurrence); + } + + /** + * Set the event occurrence attached to this message for its start lifeline + * + * @param occurrence the event occurrence to set + */ + public void setStartOccurrence(int occurrence) { + startEventOccurrence = occurrence; + if (getEndLifeline() == null) + endEventOccurrence = startEventOccurrence; + informFrame(getStartLifeline(), occurrence); + } + + /** + * Set the lifeLine which has sent the message.
+ * A new EventOccurence will be create on this lifeLine.
+ * + * @param lifeline the message sender + * @param autoCreateEvent if true, create an eventOccurence lifeline given in parameter + */ + public void autoSetStartLifeline(Lifeline lifeline) { + lifeline.getNewEventOccurrence(); + setStartLifeline(lifeline); + } + + /** + * Set the lifeLine which has received the message.
+ * A new EventOccurence will be create on this lifeLine.
+ * + * @param lifeline the message receiver + * @param autoCreateEvent if true, create an eventOccurence lifeline given in parameter + */ + public void autoSetEndLifeline(Lifeline lifeline) { + lifeline.getNewEventOccurrence(); + setEndLifeline(lifeline); + } + + /** + * Set the lifeLine which has sent the message.
+ * + * @param lifeline the message sender + */ + @Override + public void setStartLifeline(Lifeline lifeline) { + super.setStartLifeline(lifeline); + setStartOccurrence(getStartLifeline().getEventOccurrence()); + if (getEndLifeline() == null) + endEventOccurrence = startEventOccurrence; + } + + /** + * Set the lifeLine which has received the message.
+ * + * @param lifeline the message receiver + */ + @Override + public void setEndLifeline(Lifeline lifeline) { + super.setEndLifeline(lifeline); + setEventOccurrence(getEndLifeline().getEventOccurrence()); + } + + /** + * Returns true if the point C is on the segment defined with the point A and B + * + * @param xA point A x coordinate + * @param yA point A y coordinate + * @param xB point B x coordinate + * @param yB point B y coordinate + * @param xC point C x coordinate + * @param yC point C y coordinate + * @return Return true if the point C is on the segment defined with the point A and B, else otherwise + */ + protected boolean isNearSegment(int xA, int yA, int xB, int yB, int xC, int yC) { + if ((xA > xB) && (xC > xA)) + return false; + if ((xA < xB) && (xC > xB)) + return false; + if ((xA < xB) && (xC < xA)) + return false; + if ((xA > xB) && (xC < xB)) + return false; + double distAB = Math.sqrt((xB - xA) * (xB - xA) + (yB - yA) * (yB - yA)); + double scalar = ((xB - xA) * (xC - xA) + (yB - yA) * (yC - yA)) / distAB; + double distAC = Math.sqrt((xC - xA) * (xC - xA) + (yC - yA) * (yC - yA)); + double distToSegment = Math.sqrt(Math.abs(distAC * distAC - scalar * scalar)); + if (distToSegment <= Metrics.MESSAGE_SELECTION_TOLERANCE) + return true; + return false; + } + + @Override + public boolean contains(int x, int y) { + // Is it a self message? + if (startLifeline == endLifeline) { + return super.contains(x, y); + } + if (isNearSegment(getX(), getY(), getX() + getWidth(), getY() + getHeight(), x, y)) + return true; + int messageMaxWidth = Metrics.swimmingLaneWidth() - Metrics.EXECUTION_OCCURRENCE_WIDTH; + int nameWidth = getName().length() * Metrics.getAverageCharWidth(); + if (getName().length() * Metrics.getAverageCharWidth() > messageMaxWidth) { + if (Frame.contains(getX(), getY() - Metrics.MESSAGES_NAME_SPACING - Metrics.getMessageFontHeigth(), messageMaxWidth, Metrics.getMessageFontHeigth(), x, y)) + return true; + } else { + if (Frame.contains(getX() + (messageMaxWidth - nameWidth) / 2, getY() + getHeight() / 2 - Metrics.MESSAGES_NAME_SPACING - Metrics.getMessageFontHeigth(), nameWidth, Metrics.getMessageFontHeigth(), x, y)) + return true; + } + return false; + } + + protected void drawAsyncMessage(IGC context) { + if (startLifeline != null && endLifeline != null && startLifeline == endLifeline && (startEventOccurrence != endEventOccurrence)) { + int x = getX(); + int y = getY(); + int height = getHeight(); + int tempx = 0; + boolean startInActivation = isMessageStartInActivation(startEventOccurrence); + boolean endInActivation = isMessageEndInActivation(endEventOccurrence); + + if (endInActivation && !startInActivation) + tempx = Metrics.EXECUTION_OCCURRENCE_WIDTH / 2; + if (startInActivation && !endInActivation) + tempx = -Metrics.EXECUTION_OCCURRENCE_WIDTH / 2; + + int tempy = Metrics.INTERNAL_MESSAGE_WIDTH / 2; + if (getHeight() <= Metrics.INTERNAL_MESSAGE_WIDTH) + tempy = getHeight() / 2; + + context.drawLine(x, y, x + Metrics.INTERNAL_MESSAGE_WIDTH / 2, y); + context.drawLine(x + Metrics.INTERNAL_MESSAGE_WIDTH, y + tempy, x + Metrics.INTERNAL_MESSAGE_WIDTH, y + height - tempy); + context.drawLine(x + tempx, y + height, x + Metrics.INTERNAL_MESSAGE_WIDTH / 2, y + height); + + Double xt = new Double(Math.cos(0.75) * 7); + Double yt = new Double(Math.sin(0.75) * 7); + + context.drawLine(x + xt.intValue() + tempx, y + height + yt.intValue(), x + tempx, y + height); + context.drawArc(x, y, Metrics.INTERNAL_MESSAGE_WIDTH, 2 * tempy, 0, 90); + context.drawArc(x, y + height, Metrics.INTERNAL_MESSAGE_WIDTH, -2 * tempy, 0, -90); + context.drawLine(x + xt.intValue() + tempx, y + height - yt.intValue(), x + tempx, y + height); + + context.drawTextTruncated(getName(), x + Metrics.INTERNAL_MESSAGE_WIDTH + Metrics.INTERNAL_MESSAGE_V_MARGIN, y, Metrics.swimmingLaneWidth() - Metrics.EXECUTION_OCCURRENCE_WIDTH + -Metrics.INTERNAL_MESSAGE_WIDTH, + +Metrics.MESSAGES_NAME_SPACING - Metrics.getMessageFontHeigth(), !isSelected()); + } else + super.draw(context); + } + + /** + * Draws the asynchronous message in the given GC + */ + @Override + public void draw(IGC context) { + if (!isVisible()) + return; + // Draw it selected? + if (isSelected() && (startLifeline != null && endLifeline != null && startLifeline == endLifeline && (startEventOccurrence != endEventOccurrence))) { + /* + * Draw it twice First time, bigger inverting selection colors Second time, regular drawing using selection + * colors This create the highlight effect + */ + context.setForeground(Frame.getUserPref().getBackGroundColorSelection()); + context.setLineWidth(Metrics.SELECTION_LINE_WIDTH); + drawAsyncMessage(context); + context.setBackground(Frame.getUserPref().getBackGroundColorSelection()); + context.setForeground(Frame.getUserPref().getForeGroundColorSelection()); + // Second drawing is done after the else + } else { + context.setBackground(Frame.getUserPref().getBackGroundColor(prefId)); + context.setForeground(Frame.getUserPref().getForeGroundColor(prefId)); + } + if (hasFocus()) { + context.setDrawTextWithFocusStyle(true); + } + context.setLineWidth(Metrics.NORMAL_LINE_WIDTH); + drawAsyncMessage(context); + if (hasFocus()) { + context.setDrawTextWithFocusStyle(false); + } + } + + /** + * Set the time when the message end + * + * @param time the time when the message end + */ + public void setEndTime(TmfTimestamp time) { + endTime = time.clone(); + hasTime = true; + if (getStartLifeline() != null && getStartLifeline().getFrame() != null) + getStartLifeline().getFrame().setHasTimeInfo(true); + else if (getEndLifeline() != null && getEndLifeline().getFrame() != null) + getEndLifeline().getFrame().setHasTimeInfo(true); + } + + /** + * Set the time when the message start + * + * @param time the time when the message start + */ + public void setStartTime(TmfTimestamp time) { + startTime = time.clone(); + hasTime = true; + if (getStartLifeline() != null && getStartLifeline().getFrame() != null) + getStartLifeline().getFrame().setHasTimeInfo(true); + else if (getEndLifeline() != null && getEndLifeline().getFrame() != null) + getEndLifeline().getFrame().setHasTimeInfo(true); + } + + /** + * Returns the time when the message begin + * + * @return the time + */ + @Override + public TmfTimestamp getEndTime() { + return endTime; + } + + /** + * Returns the time when the message end + * + * @return the time + */ + @Override + public TmfTimestamp getStartTime() { + return startTime; + } + + @Override + public boolean hasTimeInfo() { + return hasTime; + } + + @Override + public boolean isVisible(int x, int y, int width, int height) { + int toDrawY = getY(); + int toDrawHeight = getHeight(); + if ((toDrawY > y + height + Metrics.MESSAGES_NAME_SPACING + Metrics.getMessageFontHeigth()) && (toDrawY + toDrawHeight > y + height + Metrics.MESSAGES_NAME_SPACING + Metrics.getMessageFontHeigth())) + return false; + if (toDrawY < y && (toDrawY + toDrawHeight < y)) + return false; + return super.isVisible(x, y, width, height); + } + + @Override + public Comparator getComparator() { + return new SortAsyncMessageComparator(); + } + + @Override + public String getArrayId() { + return ASYNC_MESS_TAG; + } + + @Override + public Comparator getBackComparator() { + return new SortAsyncForBackward(); + } + + @Override + public boolean positiveDistanceToPoint(int x, int y) { + int mY = getY(); + int mH = getHeight(); + if ((mY > y) || (mY + mH > y)) + return true; + return false; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/AsyncMessageReturn.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/AsyncMessageReturn.java new file mode 100755 index 0000000000..c0627a25af --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/AsyncMessageReturn.java @@ -0,0 +1,74 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: AsyncMessageReturn.java,v 1.2 2006/09/20 20:56:25 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; + +/** + * The message return graph node implementation.
+ * This class differs on the AsynMessage class only on the drawing line style (dashed instead od plain line).
+ * Message return are generally associated to a message. This means, they are connected to the same lifelines than the + * associated message but in the opposite direction and for a different event occurrence.
+ *
+ * WARNING: The association validity is not checked, it is not necessary to provide a valid association, not even needed + * to set an association to drawn a message with a message return style.
+ * + * + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.AsyncMessage AsyncMessage for usage example + * @author sveyrier + * + */ +public class AsyncMessageReturn extends AsyncMessage { + + protected AsyncMessage message; + + public static final String ASYNC_MESS_RET_TAG = "AsyncMessageRet"; //$NON-NLS-1$ + + public AsyncMessageReturn() { + prefId = ISDPreferences.PREF_ASYNC_MESS_RET; + } + + /** + * Set the associated message (the message it is the return).
+ * Setting the association will activate the navigation in the default sequence diagram implementation to the + * message when the user right click on this message return.
+ * + * @param parentMessage the message to associate + */ + public void setMessage(AsyncMessage parentMessage) { + message = parentMessage; + } + + @Override + public void draw(IGC context) { + if (!isVisible()) + return; + prefId = ISDPreferences.PREF_ASYNC_MESS_RET; + int oldStyle = context.getLineStyle(); + // Message return are dashed + context.setLineStyle(context.getLineDotStyle()); + if (!isSelected()) { + context.setBackground(Frame.getUserPref().getBackGroundColor(prefId)); + context.setForeground(Frame.getUserPref().getForeGroundColor(prefId)); + } + super.draw(context); + // restore the context + context.setLineStyle(oldStyle); + } + + @Override + public String getArrayId() { + return ASYNC_MESS_RET_TAG; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/BaseMessage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/BaseMessage.java new file mode 100755 index 0000000000..0e75899980 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/BaseMessage.java @@ -0,0 +1,660 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: BaseMessage.java,v 1.3 2008/01/24 02:28:49 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IColor; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; + +/** + * The base UML2 syncMessages implementation.
+ * This abstract class only define one event occurrence to attach to the message.
+ * Usually a message has two event occurrences attached, one for both ends. But some syncMessages(like synchronous + * syncMessages) only need one event occurrence to represent the time when they appear. Others kind of message + * representations (like asynchronous syncMessages) will be responsible to define the missing second eventOccurrence + * property.
+ *
+ * + * @see Lifeline Lifeline for more event occurence details + * @author sveyrier + */ +public abstract class BaseMessage extends GraphNode { + + /** + * The lifeline which send the message + */ + protected Lifeline startLifeline = null; + + /** + * The lifeline which receive the message + */ + protected Lifeline endLifeline = null; + + protected boolean visible = true; + + @Override + public int getX() { + // returns the exact x coordinate + return getX(false); + } + + @Override + public int getY() { + /* + * Note: lifeline.getY() return the y coordinate of the top left corner of the rectangle which contain the + * lifeline name getHeight return the height of this rectangle The message y coordinate is then relative to this + * position depending of its eventOccurrence Space between syncMessages is constant + */ + if ((startLifeline != null) && (endLifeline != null)) { + /* + * Regular message, both ends are attached to a lifeline + */ + return endLifeline.getY() + endLifeline.getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * endEventOccurrence; + + } else { + /* + * UML2 lost message kind + */ + if (startLifeline != null) { + return startLifeline.getY() + startLifeline.getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * endEventOccurrence; + } + + /* + * UML2 found message kind + */ + if (endLifeline != null) { + return endLifeline.getY() + endLifeline.getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * endEventOccurrence; + } + } + // return 0 by default + return 0; + } + + @Override + public int getWidth() { + // Returns the exact width + return getWidth(false); + } + + @Override + public int getHeight() { + return 0; + } + + /** + * Returns the graph node x coordinate.
+ * Depending on the quick parameter a approximative or exact value is return.
+ * The approximative value does not take into account if both message ends are connected to a Lifeline Execution + * Occurrence.
+ * Execution occurrence on a lifeline increase the vertical line width which represent the lifeline, this directly + * affect the message x coordinate and width.
+ *
+ * This method is typically used to faster execute none graphical operation like tooltip lookup.
+ *
+ * + * @param quick true to get an approximative value
+ * false to get the exact x value
+ * @return the graph node x coordinate + */ + protected int getX(boolean quick) { + int x = 0; + int activationWidth = Metrics.EXECUTION_OCCURRENCE_WIDTH / 2; + if ((startLifeline != null) && (endLifeline != null)) { + x = startLifeline.getX() + Metrics.getLifelineWidth() / 2; + } else { + if (startLifeline != null) { + x = startLifeline.getX() + Metrics.getLifelineWidth() / 2; + } + + if (endLifeline != null) { + x = endLifeline.getX() - Metrics.LIFELINE_SPACING / 2; + } + } + + if (quick) + return x; + + if ((startLifeline != null) && (endLifeline != null) && (startLifeline.getX() > endLifeline.getX())) { + activationWidth = -activationWidth; + } + + if (isMessageStartInActivation(endEventOccurrence)) { + x = x + activationWidth; + } + + return x; + } + + /** + * Returns the graph node width.
+ * Depending on the quick parameter a approximative or exact value is returned.
+ * The approximative value does not take into account if both message ends are connected to a Lifeline Execution + * Occurrence.
+ * Execution occurrence on a lifeline increase the vertical line width which represent the lifeline, this directly + * affect the message x coordinate and width.
+ *
+ * This method is typically used to faster execute none graphical operation like tooltip lookup.
+ *
+ * + * @param quick true to get an approximative value
+ * false to get the exact x value + * @return the graph node width + */ + protected int getWidth(boolean quick) { + int width = 0; + int activationWidth = Metrics.EXECUTION_OCCURRENCE_WIDTH / 2; + if ((startLifeline != null) && (endLifeline != null)) { + if (startLifeline == endLifeline) + width = Metrics.INTERNAL_MESSAGE_WIDTH + Metrics.EXECUTION_OCCURRENCE_WIDTH; + else + width = endLifeline.getX() + Metrics.getLifelineWidth() / 2 - getX(true); + } else { + if (startLifeline != null) { + width = Metrics.swimmingLaneWidth() / 2; + } + if (endLifeline != null) { + width = Metrics.swimmingLaneWidth() / 2; + } + } + + if (quick) + return width; + + if ((startLifeline != null) && (endLifeline != null) && (startLifeline.getX() > endLifeline.getX())) { + activationWidth = -activationWidth; + } + + if (isMessageStartInActivation(endEventOccurrence)) + width = width - activationWidth; + + if (isMessageEndInActivation(endEventOccurrence)) + width = width - activationWidth; + + return width; + } + + @Override + public boolean isVisible(int x, int y, int width, int height) { + // ***Common*** syncMessages visibility + // draw the message only if at least one end is visible + if (endLifeline != null && (endLifeline.isVisible(x, y, width, height)) || (startLifeline != null && startLifeline.isVisible(x, y, width, height))) + return true; + // In this case it can be a message which cross the whole visible area + else if (endLifeline != null && (!endLifeline.isVisible(x, y, width, height)) && (startLifeline != null && !startLifeline.isVisible(x, y, width, height))) { + if (endLifeline.getX() > x + width && startLifeline.getX() < x) + return true; + else if (startLifeline.getX() > x + width && endLifeline.getX() < x) + return true; + } + return false; + } + + public void setVisible(boolean value) { + visible = value; + } + + public boolean isVisible() { + return visible; + } + + /** + * Set the lifeline from which this message has been sent. + * + * @param lifeline - the message sender + */ + public void setStartLifeline(Lifeline lifeline) { + startLifeline = lifeline; + } + + /** + * Returns the lifeline from which this message has been sent. + * + * @return the message sender + */ + public Lifeline getStartLifeline() { + return startLifeline; + } + + /** + * Returns the lifeline which has received this message. + * + * @return the message receiver + */ + public Lifeline getEndLifeline() { + return endLifeline; + } + + /** + * Set the lifeline which has receive this message. + * + * @param lifeline the message receiver + */ + public void setEndLifeline(Lifeline lifeline) { + endLifeline = lifeline; + } + + /** + * Set the event occurrence when this message occurs.
+ * + * @param occurrence the event occurrence to assign to this message.
+ * @see Lifeline Lifeline for more event occurence details + */ + protected void setEventOccurrence(int occurrence) { + endEventOccurrence = occurrence; + } + + /** + * Returns the event occurence when is message occurs.
+ * + * @return the event occurrence assigned to this message.
+ * @see Lifeline Lifeline for more event occurence details + */ + public int getEventOccurrence() { + return endEventOccurrence; + } + + /** + * Determines if the given eventOccurence occurs on a executionOccurence owned by the sending lifeline.
+ * WARNING: this method will return a valid result only for execution occurrences which are visible in the View.
+ * As consequence this method is only used for drawing purpose, especially to determine the exact message x + * coordinate and width.
+ * + * @see BaseMessage#getX(boolean) + * @param event the event occurrence to test + * @return true if occurs on a execution occurrence owned by the sending lifeine, false otherwise + */ + protected boolean isMessageStartInActivation(int event) { + boolean inActivation = false; + if ((startLifeline != null) && (startLifeline.getExecutions() != null)) { + // int acIndex=startLifeline.getExecOccurrenceDrawIndex(); + // acIndex = first visible execution occurrence + // for drawing speed reason with only search on the visivle subset + int thisY = getY(); + for (int i = 0; i < startLifeline.getExecutions().size(); i++) { + BasicExecutionOccurrence toDraw = (BasicExecutionOccurrence) startLifeline.getExecutions().get(i); + if ((event >= toDraw.startEventOccurrence) && (event <= toDraw.endEventOccurrence)) + inActivation = true; + // if we are outside the visible area we stop right now + // This works because execution occurrences are ordered along the Y axis + if (toDraw.getY() > thisY) + break; + } + } + return inActivation; + } + + /** + * Determines if the given event occurrence occurs on a execution occurrence owned by the receiving lifeline.
+ * WARNING: this method will return a valid result only for execution occurrences which are visible in the View.
+ * As consequence this method is only used for drawing purpose, especially to determine the exact message x + * coordinate and width.
+ * + * @see BaseMessage#getX(boolean) + * @param event the event occurrence to test + * @return true if occurs on a execution occurrence owned by the receiving lifeline, false otherwise + */ + protected boolean isMessageEndInActivation(int event) { + boolean inActivation = false; + if ((endLifeline != null) && (endLifeline.getExecutions() != null)) { + // acIndex = first visible execution occurrence + // for drawing speed reason with only search on the visivle subset + for (int i = 0; i < endLifeline.getExecutions().size(); i++) { + BasicExecutionOccurrence toDraw = (BasicExecutionOccurrence) endLifeline.getExecutions().get(i); + if ((event >= toDraw.startEventOccurrence) && (event <= toDraw.endEventOccurrence)) + inActivation = true; + // if we are outside the visible area we stop right now + // This works because execution occurrences are ordered along the Y axis + if (toDraw.getY() > getY()) + break; + } + } + return inActivation; + } + + /** + * Returns true if the message or the message label contains the point given in parameter + */ + @Override + public boolean contains(int _x, int _y) { + int x = getX(); + int y = getY(); + int width = getWidth(); + int height = getHeight(); + + // Used to create a rectangle which contains the message label to allow selection when clicking the label + int tempHeight = Metrics.MESSAGES_NAME_SPACING + Metrics.getMessageFontHeigth(); + + // Is it a self message? + if (startLifeline == endLifeline) { + /* + * Rectangle.contains(x,y, width, height) does not works with negative height or width We check here if the + * rectangle width is negative. + */ + if (getName().length() * Metrics.getAverageCharWidth() > Metrics.swimmingLaneWidth() - Metrics.EXECUTION_OCCURRENCE_WIDTH / 2 + -Metrics.INTERNAL_MESSAGE_WIDTH) { + if (Frame.contains(x + Metrics.INTERNAL_MESSAGE_WIDTH + 10, y, Metrics.swimmingLaneWidth() - Metrics.EXECUTION_OCCURRENCE_WIDTH / 2 + -Metrics.INTERNAL_MESSAGE_WIDTH, Metrics.getMessageFontHeigth(), _x, _y)) + return true; + } else { + if (Frame.contains(x + Metrics.INTERNAL_MESSAGE_WIDTH + 10, y, getName().length() * Metrics.getAverageCharWidth(), Metrics.getMessageFontHeigth(), _x, _y)) + return true; + } + + // Test if the point is in part 1 of the self message + // see: "private void drawMessage (NGC context)" method for self message drawing schema + if (Frame.contains(x, y - Metrics.MESSAGE_SELECTION_TOLERANCE / 2, Metrics.INTERNAL_MESSAGE_WIDTH / 2, Metrics.MESSAGE_SELECTION_TOLERANCE, _x, _y)) + return true; + + // Test if the point is in part 3 of the self message + if (Frame.contains(x + Metrics.INTERNAL_MESSAGE_WIDTH - Metrics.MESSAGE_SELECTION_TOLERANCE / 2, y, Metrics.MESSAGE_SELECTION_TOLERANCE, height + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT, _x, _y)) + return true; + + // Test if the point is in part 5 of the self message + if (Frame.contains(x, y + height - Metrics.MESSAGE_SELECTION_TOLERANCE / 2 + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT, Metrics.INTERNAL_MESSAGE_WIDTH / 2, Metrics.MESSAGE_SELECTION_TOLERANCE, _x, _y)) + return true; + + // false otherwise + return false; + } + if (Frame.contains(x, y - tempHeight, width, tempHeight, _x, _y)) + return true; + // false otherwise + return false; + } + + protected void drawMessage(IGC context) { + int fX, fY, fW, fH; + fX = fY = fW = fH = 0; + + // temporay store the coordinates to avoid more methods calls + int x = getX(); + int y = getY(); + int width = getWidth(); + int height = getHeight(); + + // UML2 found message (always drawn from left to right) + // or UML2 lost message (always drawn from left to right) + if ((startLifeline == null || endLifeline == null) && startLifeline != endLifeline) { + // Draw the message label above the message and centered + // The label is truncated if it cannot fit between the two message end + // 2*Metrics.MESSAGES_NAME_SPACING = space above the label + space below the label + IColor temp = context.getForeground(); + context.setForeground(Frame.getUserPref().getFontColor(prefId)); + context.drawTextTruncatedCentred(getName(), x, y - Metrics.getMessageFontHeigth() - 2 * Metrics.MESSAGES_NAME_SPACING, width, 2 * Metrics.MESSAGES_NAME_SPACING + Metrics.getMessageFontHeigth(), !isSelected()); + context.setForeground(temp); + int margin = 0; + if (endLifeline == null) + margin = Metrics.MESSAGE_CIRCLE_RAY; + + // Draw the message main line + context.drawLine(x, y, x + width, y + height); + // Draw the two little lines which make a arrow part of the message + Double xt = new Double(Math.cos(0.75) * 7); + Double yt = new Double(Math.sin(0.75) * 7); + if (context.getLineStyle() == context.getLineSolidStyle()) { + IColor backcolor = context.getBackground(); + context.setBackground(context.getForeground()); + int[] points = { x + width - margin, y + height, x + width - xt.intValue() - margin, y + height - yt.intValue(), x + width - xt.intValue() - margin, y + height + yt.intValue(), x + width - margin, y + height }; + context.fillPolygon(points); + context.drawPolygon(points); + context.setBackground(backcolor); + } else { + int currentStyle = context.getLineStyle(); + int currentWidth = context.getLineWidth(); + context.setLineWidth(currentWidth + 2); + context.setLineStyle(context.getLineSolidStyle()); + context.drawLine(x + width - xt.intValue() - margin, y + height - yt.intValue(), x + width - margin, y + height); + context.drawLine(x + width - xt.intValue() - margin, y + height + yt.intValue(), x + width - margin, y + height); + context.setLineStyle(currentStyle); + context.setLineWidth(currentWidth); + } + IColor storedColor = context.getBackground(); + context.setBackground(context.getForeground()); + + // Draw a circle at the message end (endLifeline side) + int ray = Metrics.MESSAGE_CIRCLE_RAY; + if (context.getLineWidth() != Metrics.NORMAL_LINE_WIDTH) + ray = ray + Metrics.SELECTION_LINE_WIDTH - Metrics.NORMAL_LINE_WIDTH; + if (startLifeline == null) + context.fillOval(x - ray, y - ray, ray * 2, ray * 2); + else + context.fillOval(x + width - ray, y + height - ray, ray * 2, ray * 2); + context.setBackground(storedColor); + context.setForeground(Frame.getUserPref().getFontColor(prefId)); + fX = x; + fY = y - yt.intValue(); + fW = width; + fH = height + 2 * yt.intValue(); + } + // it is self message (always drawn at the left side of the owning lifeLifeline) + else if (startLifeline != null && endLifeline != null && startLifeline == endLifeline) { + /* + * Self syncMessages are drawn in 5 parts 1 -----------+ + 2 + | | | 3 | + 5 + 4 -----------+ + */ + int tempy = Metrics.INTERNAL_MESSAGE_WIDTH / 2; + if (Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT <= Metrics.INTERNAL_MESSAGE_WIDTH) + tempy = Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT / 2; + + // Part 1 + context.drawLine(x, y, x + Metrics.INTERNAL_MESSAGE_WIDTH / 2, y); + // Part 3 + context.drawLine(x + Metrics.INTERNAL_MESSAGE_WIDTH, y + tempy, x + Metrics.INTERNAL_MESSAGE_WIDTH, y + height + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT - tempy); + // Part 5 + context.drawLine(x, y + height + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT, x + Metrics.INTERNAL_MESSAGE_WIDTH / 2, y + height + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT); + + Double xt = new Double(Math.cos(0.75) * 7); + Double yt = new Double(Math.sin(0.75) * 7); + + fX = x; + fY = y; + fW = Metrics.INTERNAL_MESSAGE_WIDTH; + fH = height + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT; + + // Draw the two little lines which make a arrow part of the message + if (context.getLineStyle() == context.getLineSolidStyle()) { + IColor backcolor = context.getBackground(); + context.setBackground(context.getForeground()); + int[] points = { x, y + height + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT, x + xt.intValue(), y + height + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT + yt.intValue(), x + xt.intValue(), + y + height + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT - yt.intValue(), x, y + height + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT }; + context.fillPolygon(points); + context.drawPolygon(points); + context.setBackground(backcolor); + } else { + int currentStyle = context.getLineStyle(); + int currentWidth = context.getLineWidth(); + context.setLineWidth(currentWidth + 2); + context.setLineStyle(context.getLineSolidStyle()); + context.drawLine(x + xt.intValue(), y + height + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT + yt.intValue(), x, y + height + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT); + context.drawLine(x + xt.intValue(), y + height + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT - yt.intValue(), x, y + height + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT); + context.setLineStyle(currentStyle); + context.setLineWidth(currentWidth); + } + + // Part 2 + context.drawArc(x, y, Metrics.INTERNAL_MESSAGE_WIDTH, 2 * tempy, 0, 90); + // Part 4 + context.drawArc(x, y + Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT, Metrics.INTERNAL_MESSAGE_WIDTH, -2 * tempy, 0, -90); + + // Draw the message label above the message and centered + // The label is truncated if it cannot fit between the two message end + // 2*Metrics.MESSAGES_NAME_SPACING = space above the label + space below the label + + // the space available for the text is sorter if are drawing internal message on the last lifeline + context.setForeground(Frame.getUserPref().getFontColor(prefId)); + if (startLifeline.getIndex() == startLifeline.getFrame().getHorizontalIndex()) + context.drawTextTruncated(getName(), x + width + Metrics.INTERNAL_MESSAGE_V_MARGIN / 2, y, Metrics.swimmingLaneWidth() / 2 - Metrics.EXECUTION_OCCURRENCE_WIDTH + -Metrics.INTERNAL_MESSAGE_WIDTH, +Metrics.MESSAGES_NAME_SPACING + - Metrics.getMessageFontHeigth(), !isSelected()); + else + context.drawTextTruncated(getName(), x + width + Metrics.INTERNAL_MESSAGE_V_MARGIN / 2, y, Metrics.swimmingLaneWidth() - Metrics.EXECUTION_OCCURRENCE_WIDTH + -Metrics.INTERNAL_MESSAGE_WIDTH, + +Metrics.MESSAGES_NAME_SPACING - Metrics.getMessageFontHeigth(), !isSelected()); + } + // it is regular message + else if (startLifeline != null && endLifeline != null) { + // Draw the message main line + context.drawLine(x, y, x + width, y + height); + + int spaceBTWStartEnd = endLifeline.getX() - startLifeline.getX(); + + double a = height; + double b = width; + double angle = Math.atan(a / b); + // Compute the coordinates of the two little lines which make the arrow part of the message + int sign = 1; + if (spaceBTWStartEnd < 0) + sign = -1; + Double x1 = new Double(sign * Math.cos(angle - 0.75) * 7); + Double y1 = new Double(sign * Math.sin(angle - 0.75) * 7); + Double x2 = new Double(sign * Math.cos(angle + 0.75) * 7); + Double y2 = new Double(sign * Math.sin(angle + 0.75) * 7); + + fX = getX(); + fY = y + height - y2.intValue(); + fW = getWidth(); + fH = y2.intValue() - y1.intValue() + 1; + if (fW < 0) { + fW = -fW; + fX = fX - fW; + } + + if (fH < 0) { + fH = -fH; + fY = fY - fH; + } + + // Draw the two little lines which make a arrow part of the message + if (context.getLineStyle() == context.getLineSolidStyle()) { + IColor backcolor = context.getBackground(); + context.setBackground(context.getForeground()); + int[] points = { x + width - x1.intValue(), y + height - y1.intValue(), x + width, y + height, x + width - x2.intValue(), y + height - y2.intValue(), x + width - x1.intValue(), y + height - y1.intValue() }; + context.fillPolygon(points); + context.drawPolygon(points); + context.setBackground(backcolor); + } else { + int currentStyle = context.getLineStyle(); + int currentWidth = context.getLineWidth(); + context.setLineWidth(currentWidth + 2); + context.setLineStyle(context.getLineSolidStyle()); + context.drawLine(x + width - x1.intValue(), y + height - y1.intValue(), x + width, y + height); + context.drawLine(x + width - x2.intValue(), y + height - y2.intValue(), x + width, y + height); + context.setLineStyle(currentStyle); + context.setLineWidth(currentWidth); + } + + // Draw the message label above the message and centered + // The label is truncated if it cannot fit between the two message end + // 2*Metrics.MESSAGES_NAME_SPACING = space above the label + space below the label + context.setForeground(Frame.getUserPref().getFontColor(prefId)); + if (spaceBTWStartEnd > 0) + context.drawTextTruncatedCentred(getName(), x, y + height / 2 - (2 * Metrics.MESSAGES_NAME_SPACING + Metrics.getMessageFontHeigth()), width, 2 * Metrics.MESSAGES_NAME_SPACING + Metrics.getMessageFontHeigth(), !isSelected()); + else + context.drawTextTruncatedCentred(getName(), x + width, y + height / 2 - (2 * Metrics.MESSAGES_NAME_SPACING + Metrics.getMessageFontHeigth()), -width, 2 * Metrics.MESSAGES_NAME_SPACING + +Metrics.getMessageFontHeigth(), !isSelected()); + } + } + + @Override + public void draw(IGC context) { + if (!isVisible()) + return; + // Draw it selected?*/ + if (isSelected()) { + + /* + * Draw it twice First time, bigger inverting selection colors Second time, regular drawing using selection + * colors This create the highlight effect + */ + context.setForeground(Frame.getUserPref().getBackGroundColorSelection()); + context.setLineWidth(Metrics.SELECTION_LINE_WIDTH); + drawMessage(context); + context.setBackground(Frame.getUserPref().getBackGroundColorSelection()); + context.setForeground(Frame.getUserPref().getForeGroundColorSelection()); + // Second drawing is done after + } + context.setLineWidth(Metrics.NORMAL_LINE_WIDTH); + if (hasFocus()) { + context.setDrawTextWithFocusStyle(true); + } + drawMessage(context); + int oldStyle = context.getLineStyle(); + if (hasFocus()) { + context.setDrawTextWithFocusStyle(false); + drawFocus(context); + } + // restore the context + context.setLineStyle(oldStyle); + } + + /** + * Determine if two messages are identical. This default implementation considers that overlapping messages with + * same coordinates are identical. + * + * @param message - the message to compare with + * @return true if identical false otherwise + */ + @Override + public boolean isSameAs(GraphNode message) { + if (message == null) + return false; + if (!(message instanceof BaseMessage)) + return super.isSameAs(message); + if (getX() == message.getX() && getY() == message.getY() && getWidth() == message.getWidth() && getHeight() == message.getHeight()) + return true; + else + return false; + } + + public void drawRot(int x, int y, int w, int h, IGC context) { + double angleA = Math.atan2(getHeight(), getWidth()); + double cosA = Math.cos(angleA); + double sinA = Math.sin(angleA); + + int gx = getX(); + int gy = getY(); + + h = h / 2; + + double cw = Math.sqrt(w * w + getHeight() * getHeight()); + + int x1 = Math.round((float) ((x - gx) * cosA - (y - gy) * sinA)); + int y1 = Math.round((float) ((x - gx) * sinA + (y - gy) * cosA)); + + int x2 = Math.round((float) (cw * cosA - (y - gy) * sinA)); + int y2 = Math.round((float) (cw * sinA + (y - gy) * cosA)); + + int x3 = Math.round((float) (cw * cosA - (h) * sinA)); + int y3 = Math.round((float) (cw * sinA + (h) * cosA)); + + int x4 = Math.round((float) ((x - gx) * cosA - (h) * sinA)); + int y4 = Math.round((float) ((x - gx) * sinA + (h) * cosA)); + + int[] points = { x1 + getX(), y1 + getY(), x2 + getX(), y2 + getY(), x3 + getX(), y3 + getY(), x4 + getX(), y4 + getY() }; + context.drawPolygon(points); + } + + @Override + public void drawFocus(IGC context) { + if ((startLifeline != endLifeline) && (startEventOccurrence == endEventOccurrence)) { + context.setLineStyle(context.getLineDotStyle()); + context.setLineWidth(Metrics.NORMAL_LINE_WIDTH); + context.setBackground(Frame.getUserPref().getBackGroundColorSelection()); + context.setForeground(Frame.getUserPref().getForeGroundColorSelection()); + context.drawFocus(getX(), getY() - 3, getWidth(), getHeight() + 6); + } else if ((startLifeline == endLifeline) && (startEventOccurrence == endEventOccurrence)) { + context.drawFocus(getX(), getY() - 3, getWidth(), Metrics.SYNC_INTERNAL_MESSAGE_HEIGHT + 6); + } else if ((startLifeline != endLifeline) && (startEventOccurrence != endEventOccurrence)) { + context.setLineStyle(context.getLineDotStyle()); + context.setLineWidth(Metrics.NORMAL_LINE_WIDTH); + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_LIFELINE_HEADER)); + context.setForeground(Frame.getUserPref().getForeGroundColor(ISDPreferences.PREF_LIFELINE_HEADER)); + drawRot(getX(), getY() - 5, getWidth(), 10, context); + } else + super.drawFocus(context); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/BasicExecutionOccurrence.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/BasicExecutionOccurrence.java new file mode 100755 index 0000000000..c89253f42e --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/BasicExecutionOccurrence.java @@ -0,0 +1,232 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: BasicExecutionOccurrence.java,v 1.2 2006/09/20 20:56:25 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IColor; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; + +/** + * BasicExecutionOccurrence is the UML2 execution occurrence graphical representation. It is attached to one Lifeline, + * the event occurrence "duration" along the lifeline is defined by two event occurrences + * + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline Lifeline for more event occurence details + * @author sveyrier + * + */ +public class BasicExecutionOccurrence extends GraphNode { + + protected Lifeline lifeline = null; + + public static final String EXEC_OCC_TAG = "Execution_Occ"; //$NON-NLS-1$ + + public BasicExecutionOccurrence() { + prefId = ISDPreferences.PREF_EXEC; + } + + @Override + public int getX() { + if (lifeline == null) + return 0; + return lifeline.getX() + Metrics.getLifelineWidth() / 2 - Metrics.EXECUTION_OCCURRENCE_WIDTH / 2; + } + + @Override + public int getY() { + if (lifeline == null) + return 0; + return lifeline.getY() + lifeline.getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * startEventOccurrence; + } + + @Override + public int getWidth() { + if (lifeline == null) + return 0; + return Metrics.EXECUTION_OCCURRENCE_WIDTH; + } + + @Override + public int getHeight() { + if (lifeline == null) + return 0; + return ((Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing())) * (endEventOccurrence - startEventOccurrence); + } + + @Override + public boolean contains(int _x, int _y) { + int x = getX(); + int y = getY(); + int width = getWidth(); + int height = getHeight(); + + if (Frame.contains(x, y, width, height, _x, _y)) { + return true; + } + + if (getNodeAt(_x, _y) != null) + return true; + return false; + } + + @Override + public String getName() { + if (super.getName() == null || super.getName().equals("")) //$NON-NLS-1$ + return lifeline.getToolTipText(); + else + return super.getName(); + } + + /** + * Set the lifeline on which the execution occurrence appears. + * + * @param theLifeline - the parent lifeline + */ + public void setLifeline(Lifeline theLifeline) { + lifeline = theLifeline; + } + + /** + * Get the lifeline on which the execution occurrence appears. + * + * @return - the parent lifeline + */ + public Lifeline getLifeline() { + return lifeline; + } + + /** + * Get the execution start event occurrence + * + * @return the start event occurrence to set + */ + @Override + public int getStartOccurrence() { + return startEventOccurrence; + } + + /** + * Set the execution end event occurrence + * + * @return the end event occurrence to set + */ + @Override + public int getEndOccurrence() { + return endEventOccurrence; + } + + /** + * Set the execution start event occurrence + * + * @param occurrence the start event occurrence to set + */ + public void setStartOccurrence(int occurrence) { + startEventOccurrence = occurrence; + } + + /** + * Set the execution end event occurrence + * + * @param occurrence the end event occurrence to set + */ + public void setEndOccurrence(int occurrence) { + endEventOccurrence = occurrence; + } + + @Override + public void draw(IGC context) { + int x = getX(); + int y = getY(); + int width = getWidth(); + int height = getHeight(); + IColor tempFillColor = null, tempStrokeColor = null; + + // The execution occurrence is selected + // if the owning lifeline is selected + if (lifeline.isSelected() || isSelected()) { + context.setBackground(Frame.getUserPref().getBackGroundColorSelection()); + context.setForeground(Frame.getUserPref().getForeGroundColorSelection()); + } else { + tempFillColor = setUnselectedFillColor(context); + } + if (Frame.getUserPref().useGradienColor()) + context.fillGradientRectangle(x, y, width, height, false); + else + context.fillRectangle(x, y, width, height); + tempStrokeColor = setUnselectedStrokeColor(context); + context.drawRectangle(x, y, width, height); + if (tempFillColor != null) { + tempFillColor.dispose(); + tempFillColor = null; + } + if (tempStrokeColor != null) { + tempStrokeColor.dispose(); + tempStrokeColor = null; + } + if (hasFocus()) + drawFocus(context); + super.drawChildenNodes(context); + } + + /** + * Rewrite this method in your extension in order to support customized fill colors + * + * @param context + * @return IColor + */ + protected IColor setUnselectedFillColor(IGC context) { + if (Frame.getUserPref().useGradienColor()) { + context.setGradientColor(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_EXEC)); + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_FRAME)); + } else + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_EXEC)); + return null; + } + + /** + * Rewrite this method in your extension in order to support customized stroke colors + * + * @param context + * @return IColor + */ + protected IColor setUnselectedStrokeColor(IGC context) { + context.setForeground(Frame.getUserPref().getForeGroundColor(ISDPreferences.PREF_EXEC)); + return null; + } + + @Override + public String getArrayId() { + return EXEC_OCC_TAG; + } + + @Override + public boolean positiveDistanceToPoint(int x, int y) { + if (getY() + getHeight() > y) + return true; + return false; + } + + @Override + public boolean isVisible(int x, int y, int width, int height) { + if ((getLifeline() != null) && (getLifeline().isVisible(x, y, width, height))) { + int ly = getY(); + int lh = getHeight(); + if (ly >= y && ly < y + height) + return true; + if (ly + lh > y && ly + lh <= y + height) + return true; + if ((ly < y) && (ly + lh > y + height)) + return true; + } + return false; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/BasicFrame.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/BasicFrame.java new file mode 100755 index 0000000000..befab048d7 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/BasicFrame.java @@ -0,0 +1,520 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: BasicFrame.java,v 1.2 2008/01/24 02:28:49 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.preferences.SDViewPref; + +/** + * The Frame class is the base sequence diagram graph nodes container.
+ * For instance, only one frame can be drawn in the View.
+ * Lifelines, Messages and Stop which are supposed to represent a Sequence diagram are drawn in a Frame.
+ * Only the graph node added to their representing list will be drawn. + * + * The lifelines are appended along the X axsis when added in a frame.
+ * The syncMessages are ordered along the Y axsis depending on the event occurrence they are attached to.
+ * + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline Lifeline for more event occurence details + * @author sveyrier + * @version 1.0 + */ +public class BasicFrame extends GraphNode { + + /** + * Contains the max elapsed time between two consecutive messages in the whole frame + */ + protected TmfTimestamp maxTime = new TmfTimestamp(0); + /** + * Contains the min elapsed time between two consecutive messages in the whole frame + */ + protected TmfTimestamp minTime = new TmfTimestamp(0); + + /** + * Indicate if the min and max elapsed time between two consecutive messages in the whole frame need to be computed + */ + protected boolean computeMinMax = true; + + /** + * Store the preference set by the user regarding the external time. This flag is used determine if the min and max + * need to be recomputed in case this preference is changed. + */ + protected boolean lastExternalTimePref = SDViewPref.getInstance().excludeExternalTime(); + + /** + * The greater event occurrence created on graph nodes drawn in this Frame This directly impact the Frame height + */ + protected int verticalIndex = 0; + + /** + * The index along the x axis where the next lifeline will is drawn This directly impact the Frame width + */ + protected int horizontalIndex = 0; + + protected boolean timeInfo = false; + + /** + * The current Frame visible area + */ + protected int visibleAreaX; + protected int visibleAreaY; + protected int visibleAreaWidth; + protected int visibleAreaHeight; + + protected static ISDPreferences userPref = null; + + protected int forceEventOccurrenceSpacing = -1; + + protected boolean customMinMax = false; + + protected TmfTimestamp minSDTime = new TmfTimestamp(); + protected TmfTimestamp maxSDTime = new TmfTimestamp(); + protected boolean initSDMin = true; + + /** + * Creates an empty frame. + */ + public BasicFrame() { + Metrics.setForcedEventSpacing(forceEventOccurrenceSpacing); + } + + /** + * + * Returns the greater event occurence known by the Frame + * + * @return the greater event occurrence + */ + protected int getMaxEventOccurrence() { + return verticalIndex; + } + + /** + * Set the greater event occurrence created in GraphNodes included in the frame + * + * @param eventOccurrence the new greater event occurrence + */ + protected void setMaxEventOccurrence(int eventOccurrence) { + verticalIndex = eventOccurrence; + } + + /** + * This method increase the lifeline place holder The return value is usually assign to a lifeline. This can be used + * to set the lifelines drawing order. Also, calling this method two times and assigning only the last given index + * to a lifeline will increase this lifeline draw spacing (2 times the default spacing) from the last added + * lifeline. + * + * @return a new lifeline index + */ + protected int getNewHorizontalIndex() { + return ++horizontalIndex; + } + + /** + * Returns the current horizontal index + * + * @return the current horizontal index + * @see Frame#getNewHorizontalIndex() for horizontal index description + */ + protected int getHorizontalIndex() { + return horizontalIndex; + } + + /** + * Add a GraphNode into the frame + * + * @param nodeToAdd the node to add + */ + @Override + public void addNode(GraphNode nodeToAdd) { + computeMinMax = true; + super.addNode(nodeToAdd); + } + + /** + * @return the frame x axis value in the containing view + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode#getX() + */ + @Override + public int getX() { + return Metrics.FRAME_H_MARGIN; + } + + /** + * @return the frame y axis value in the containing view + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode#getX() + */ + @Override + public int getY() { + return Metrics.FRAME_V_MARGIN; + } + + /** + * The frame width depends on the number of lifeline added in the frame + * + * @return the frame width + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode#getWidth() + */ + @Override + public int getWidth() { + if (horizontalIndex == 0) + return 3 * Metrics.swimmingLaneWidth() + Metrics.LIFELINE_H_MAGIN * 2 - Metrics.FRAME_H_MARGIN - Metrics.LIFELINE_SPACING / 2; + else + return horizontalIndex * Metrics.swimmingLaneWidth() + Metrics.LIFELINE_H_MAGIN * 2 + 1 - Metrics.LIFELINE_SPACING; + } + + /** + * The Frame height depends on the maximum number of messages added to a lifeline( Taking all lifelines into + * account) + * + * @return the frame height + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode#getHeight() + */ + @Override + public int getHeight() { + if (verticalIndex == 0) + return 5 * (Metrics.getMessagesSpacing() + Metrics.getMessageFontHeigth()) + Metrics.LIFELINE_NAME_H_MARGIN + Metrics.FRAME_NAME_H_MARGIN + Metrics.getFrameFontHeigth() + Metrics.LIFELINE_VT_MAGIN + Metrics.LIFELINE_VB_MAGIN + + Metrics.LIFELINE_NAME_H_MARGIN + Metrics.FRAME_NAME_H_MARGIN + Metrics.getLifelineFontHeigth() * 2; + if (forceEventOccurrenceSpacing >= 0) + Metrics.setForcedEventSpacing(forceEventOccurrenceSpacing); + return verticalIndex * (Metrics.getMessagesSpacing() + Metrics.getMessageFontHeigth()) + Metrics.LIFELINE_NAME_H_MARGIN + Metrics.FRAME_NAME_H_MARGIN + Metrics.getFrameFontHeigth() + Metrics.LIFELINE_VT_MAGIN + Metrics.LIFELINE_VB_MAGIN + + Metrics.LIFELINE_NAME_H_MARGIN + Metrics.FRAME_NAME_H_MARGIN + Metrics.getLifelineFontHeigth() * 2; + } + + /** + * Returns the graph node which contains the point given in parameter for the given graph node list and starting the + * iteration at the given index
+ * WARNING: Only graph nodes with smaller coordinates than the current visible area can be returned.
+ * + * @param x the x coordinate of the point to test + * @param y the y coordinate of the point to test + * @param list the list to search in + * @param fromIndex list browsing starting point + * @return the graph node containing the point given in parameter, null otherwise + */ + @Override + protected GraphNode getNodeFromListAt(int x, int y, List list, int fromIndex) { + if (list == null) + return null; + for (int i = fromIndex; i < list.size(); i++) { + GraphNode node = (GraphNode) list.get(i); + // only lifeline list is x ordered + // Stop browsing the list if the node is outside the visible area + // all others nodes will be not visible + if ((node instanceof Lifeline) && (node.getX() > visibleAreaX + visibleAreaWidth)) + break; + if (node.getHeight() < 0) { + if (node.getY() + node.getHeight() > visibleAreaY + visibleAreaHeight) + break; + } else { + if (node.getY() > visibleAreaY + visibleAreaHeight) + break; + } + if (node.contains(x, y)) + return node; + } + return null; + } + + /** + * Draw the Frame rectangle + * + * @param context the context to draw to + */ + protected void drawFrame(IGC context) { + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_FRAME)); + context.setForeground(Frame.getUserPref().getForeGroundColor(ISDPreferences.PREF_FRAME)); + + int x = getX(); + int y = getY(); + int w = getWidth(); + int h = getHeight(); + + // Draw the frame main rectangle + context.fillRectangle(x, y, w, h); + context.drawRectangle(x, y, w, h); + + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_FRAME_NAME)); + context.setForeground(Frame.getUserPref().getForeGroundColor(ISDPreferences.PREF_FRAME_NAME)); + context.setFont(Frame.getUserPref().getFont(ISDPreferences.PREF_FRAME_NAME)); + + int nameWidth = context.textExtent(getName()) + 2 * Metrics.FRAME_NAME_V_MARGIN; + int nameHeight = Metrics.getFrameFontHeigth() + +Metrics.FRAME_NAME_H_MARGIN * 2; + + // Draw the frame name area + if (nameWidth > w) + nameWidth = w; + + int[] points = { x, y, x + nameWidth, y, x + nameWidth, y - 11 + nameHeight, x - 11 + nameWidth, y + nameHeight, x, y + nameHeight, x, y + nameHeight }; + context.fillPolygon(points); + context.drawPolygon(points); + context.drawLine(x, y, x, y + nameHeight); + + context.setForeground(Frame.getUserPref().getFontColor(ISDPreferences.PREF_FRAME_NAME)); + context.drawTextTruncatedCentred(getName(), x, y, nameWidth - 11, nameHeight, false); + + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_FRAME)); + context.setForeground(Frame.getUserPref().getForeGroundColor(ISDPreferences.PREF_FRAME)); + } + + /** + * Draws the Frame on the given context.
+ * This method start width GraphNodes ordering if needed.
+ * After, depending on the visible area, only visible GraphNodes are drawn.
+ * + * @param context the context to draw to + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode#draw(IGC) + */ + @Override + public void draw(IGC context) { + draw(context, true); + } + + /** + * Draws the Frame on the given context.
+ * This method start width GraphNodes ordering if needed.
+ * After, depending on the visible area, only visible GraphNodes are drawn.
+ * + * @param context the context to draw to + * @param drawFrame indicate if the frame rectangle need to be redrawn + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode#draw(IGC) + */ + protected void draw(IGC context, boolean drawFrame) { + visibleAreaHeight = context.getVisibleHeight(); + visibleAreaWidth = context.getVisibleWidth(); + visibleAreaX = context.getContentsX(); + visibleAreaY = context.getContentsY(); + + if (forceEventOccurrenceSpacing >= 0) + Metrics.setForcedEventSpacing(forceEventOccurrenceSpacing); + else + Metrics.setForcedEventSpacing(-1); + if (userPref == null) + return; + super.drawChildenNodes(context); + } + + public static void setUserPref(ISDPreferences pref) { + userPref = pref; + } + + public static ISDPreferences getUserPref() { + return userPref; + } + + public void forceEventOccurrenceSpacing(int space) { + forceEventOccurrenceSpacing = space; + } + + /** + * Return the X coordinates of the frame visible area + * + * @return the X coordinates of the frame visible area + */ + public int getVisibleAreaX() { + return visibleAreaX; + } + + /** + * Return the frame visible area width + * + * @return the frame visible area width + */ + public int getVisibleAreaWidth() { + return visibleAreaWidth; + } + + /** + * Return the frame visible area height + * + * @return the frame visible area height + */ + public int getVisibleAreaHeight() { + return visibleAreaHeight; + } + + /** + * Return the X coordinates of the frame visible area + * + * @return the X coordinates of the frame visible area + */ + public int getVisibleAreaY() { + return visibleAreaY; + } + + /** + * Return the minimum time stored in the frame taking all GraphNodes into account + * + * @return the minimum GraphNode time + */ + public TmfTimestamp getMinTime() { + if (lastExternalTimePref != SDViewPref.getInstance().excludeExternalTime()) { + lastExternalTimePref = SDViewPref.getInstance().excludeExternalTime(); + computeMinMax = true; + } + if ((computeMinMax) && (!customMinMax)) { + computeMinMax(); + computeMinMax = false; + } + return minTime; + } + + public void setMin(TmfTimestamp min) { + minTime = min; + customMinMax = true; + } + + public void setMax(TmfTimestamp max) { + maxTime = max; + customMinMax = true; + } + + public void resetCustomMinMax() { + customMinMax = false; + computeMinMax = true; + } + + /** + * Return the maximum time stored in the frame taking all GraphNodes into account + * + * @return the maximum GraphNode time + */ + public TmfTimestamp getMaxTime() { + if (lastExternalTimePref != SDViewPref.getInstance().excludeExternalTime()) { + lastExternalTimePref = SDViewPref.getInstance().excludeExternalTime(); + computeMinMax = true; + } + if (computeMinMax) { + computeMinMax(); + computeMinMax = false; + } + return maxTime; + } + + protected void computeMaxMinTime() { + if (!initSDMin) + return; + + List timeArray = buildTimeArray(); + if (timeArray == null) + return; + for (int i = 0; i < timeArray.size(); i++) { + SDTimeEvent m = (SDTimeEvent) timeArray.get(i); + + if (m.getTime().compareTo(maxSDTime, true) > 0) { + maxSDTime = m.getTime(); + } + + if ((m.getTime().compareTo(minSDTime, true) < 0) || (initSDMin == true)) { + minSDTime = m.getTime(); + initSDMin = false; + } + } + } + + public TmfTimestamp getSDMinTime() { + computeMaxMinTime(); + return minSDTime; + } + + public TmfTimestamp getSDMaxTime() { + computeMaxMinTime(); + return maxSDTime; + } + + /** + * Browse all the GraphNode to compute the min and max times store in the Frame + */ + protected void computeMinMax() { + List timeArray = buildTimeArray(); + if (timeArray == null) + return; + for (int i = 0; i < timeArray.size() - 1; i++) { + SDTimeEvent m1 = (SDTimeEvent) timeArray.get(i); + SDTimeEvent m2 = (SDTimeEvent) timeArray.get(i + 1); + + updateMinMax(m1, m2); + + } + } + + protected void updateMinMax(SDTimeEvent m1, SDTimeEvent m2) { + TmfTimestamp delta = m2.getTime().getDelta(m1.getTime()); + if (computeMinMax) { + minTime = delta.clone(); + if (minTime.compareTo(TmfTimestamp.Zero, false) < 0) { + minTime = new TmfTimestamp(0, m1.getTime().getScale(), m1.getTime().getPrecision()); + } + maxTime = minTime.clone(); + computeMinMax = false; + } + + if ((delta.compareTo(minTime, true) < 0) && (delta.compareTo(TmfTimestamp.Zero, false) > 0)) { + minTime = delta.clone(); + } + + if ((delta.compareTo(maxTime, true) > 0) && (delta.compareTo(TmfTimestamp.Zero, false) > 0)) { + maxTime = delta.clone(); + } + } + + protected List buildTimeArray() { + if (!hasChilden) + return null; + + Iterator it = fSort.keySet().iterator(); + List timeArray = new ArrayList(); + while (it.hasNext()) { + String nodeType = it.next(); + List list = (List) nodes.get(nodeType); + for (int i = 0; i < list.size(); i++) { + Object timedNode = list.get(i); + if ((timedNode instanceof ITimeRange) && ((ITimeRange) timedNode).hasTimeInfo()) { + int event = ((GraphNode) list.get(i)).getStartOccurrence(); + TmfTimestamp time = ((ITimeRange) list.get(i)).getStartTime(); + SDTimeEvent f = new SDTimeEvent(time, event, (ITimeRange) list.get(i)); + timeArray.add(f); + if (event != ((GraphNode) list.get(i)).getEndOccurrence()) { + event = ((AsyncMessage) list.get(i)).getEndOccurrence(); + time = ((ITimeRange) list.get(i)).getEndTime(); + f = new SDTimeEvent(time, event, (ITimeRange) list.get(i)); + timeArray.add(f); + } + } + } + } + return timeArray; + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode#getArrayId() + */ + @Override + public String getArrayId() { + return null; + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode#contains(int, int) + */ + @Override + public boolean contains(int x, int y) { + return false; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/EllipsisisMessage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/EllipsisisMessage.java new file mode 100755 index 0000000000..5747530cb3 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/EllipsisisMessage.java @@ -0,0 +1,127 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: EllipsisisMessage.java,v 1.3 2008/01/24 02:29:19 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IColor; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; + +public class EllipsisisMessage extends AsyncMessage implements ITimeRange { + + @Override + public int getX() { + if (startLifeline == null) + return super.getX() + super.getWidth() - 16; + else + return super.getX(); + } + + @Override + public int getY() { + return super.getY() + 3; + } + + @Override + public int getWidth() { + return 16; + } + + @Override + protected void drawMessage(IGC context) { + // temporay store the coordinates to avoid more methods calls + int x = super.getX(); + int y = getY(); + int width = super.getWidth(); + int height = getHeight(); + + // UML2 found message (always drawn from left to right) + if (startLifeline == null && endLifeline != null) { + // Draw the message label above the message and centered + // The label is truncated if it cannot fit between the two message end + // 2*Metrics.MESSAGES_NAME_SPACING = space above the label + space below the label + context.drawTextTruncatedCentred(getName(), x, y - Metrics.getMessageFontHeigth() - 2 * Metrics.MESSAGES_NAME_SPACING, width, 2 * Metrics.MESSAGES_NAME_SPACING + Metrics.getMessageFontHeigth(), !isSelected()); + + int currentStyle = context.getLineStyle(); + context.setLineStyle(context.getLineSolidStyle()); + // Draw the message main line + context.drawRectangle(x + width - 5, y, x + width - 6, y + height); + context.drawRectangle(x + width - 10, y, x + width - 11, y + height); + context.drawRectangle(x + width - 15, y, x + width - 16, y + height); + context.setLineStyle(currentStyle); + + IColor storedColor = context.getBackground(); + context.setBackground(context.getForeground()); + context.fillRectangle(x + width - 5, y, x + width - 6, y + height); + context.fillRectangle(x + width - 10, y, x + width - 11, y + height); + context.fillRectangle(x + width - 15, y, x + width - 16, y + height); + context.setBackground(storedColor); + } + // UML2 lost message (always drawn from left to right) + else if (endLifeline == null && startLifeline != null) { + // Draw the message label above the message and centered + // The label is truncated if it cannot fit between the two message end + // 2*Metrics.MESSAGES_NAME_SPACING = space above the label + space below the label + context.drawTextTruncatedCentred(getName(), x, y - Metrics.getMessageFontHeigth() - 2 * Metrics.MESSAGES_NAME_SPACING, width, 2 * Metrics.MESSAGES_NAME_SPACING + Metrics.getMessageFontHeigth(), !isSelected()); + + int currentStyle = context.getLineStyle(); + context.setLineStyle(context.getLineSolidStyle()); + // Draw the message main line + context.drawRectangle(x + 5, y, 1, 1); + context.drawRectangle(x + 10, y, 1, 1); + context.drawRectangle(x + 15, y, 1, 1); + + context.setLineStyle(currentStyle); + + IColor storedColor = context.getBackground(); + context.setBackground(context.getForeground()); + context.fillRectangle(x + 5, y, 1, 1); + context.fillRectangle(x + 10, y, 1, 1); + context.fillRectangle(x + 15, y, 1, 1); + + context.setBackground(storedColor); + + } else + super.draw(context); + } + + @Override + public void draw(IGC context) { + if (!isVisible()) + return; + // Draw it selected?*/ + if (isSelected()) { + /* + * Draw it twice First time, bigger inverting selection colors Second time, regular drawing using selection + * colors This create the highlight effect + */ + context.setForeground(Frame.getUserPref().getBackGroundColorSelection()); + context.setLineWidth(Metrics.SELECTION_LINE_WIDTH); + drawMessage(context); + context.setBackground(Frame.getUserPref().getBackGroundColorSelection()); + context.setForeground(Frame.getUserPref().getForeGroundColorSelection()); + // Second drawing is done after the else + } else { + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_ASYNC_MESS)); + context.setForeground(Frame.getUserPref().getForeGroundColor(ISDPreferences.PREF_ASYNC_MESS)); + } + if (hasFocus()) { + context.setDrawTextWithFocusStyle(true); + } + context.setLineWidth(Metrics.NORMAL_LINE_WIDTH); + drawMessage(context); + context.setLineWidth(Metrics.NORMAL_LINE_WIDTH); + if (hasFocus()) { + context.setDrawTextWithFocusStyle(false); + } + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/ExecutionOccurrence.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/ExecutionOccurrence.java new file mode 100755 index 0000000000..5b28c32d3b --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/ExecutionOccurrence.java @@ -0,0 +1,194 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ExecutionOccurrence.java,v 1.2 2006/09/20 20:56:25 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IColor; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IImage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; + +/** + * ExecutionOccurrence is the UML2 execution occurrence graphical representation. It is a BasicExecutionOccurrence on + * which you can customize fill and/or + * + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline Lifeline for more event occurence details + * @author sveyrier + * + */ +public class ExecutionOccurrence extends BasicExecutionOccurrence implements ITimeRange { + + protected int[] fillRGB; + protected int[] strokeRGB; + protected IImage image, ellipsesImage; + + protected TmfTimestamp startTime, endTime; + protected boolean hasTime; + + /** + * Set the lifeline on which the execution occurrence appears. + * + * @param theLifeline - the parent lifeline + */ + @Override + public void setLifeline(Lifeline theLifeline) { + super.setLifeline(theLifeline); + if (lifeline != null && hasTime) { + lifeline.hasTime = true; + if (lifeline.getFrame() != null) { + lifeline.getFrame().setHasTimeInfo(true); + } + } + } + + /** + * Set the red, green and blue value of the optional color to be used for filling the execution occurrence + * + * @param _r + * @param _g + * @param _b + */ + public void setFillColor(int _r, int _g, int _b) { + fillRGB = new int[3]; + fillRGB[0] = _r; + fillRGB[1] = _g; + fillRGB[2] = _b; + } + + /** + * Set the red, green and blue value of the optional color to be used for drawing the execution occurrence + * + * @param _r + * @param _g + * @param _b + */ + public void setStrokeColor(int _r, int _g, int _b) { + strokeRGB = new int[3]; + strokeRGB[0] = _r; + strokeRGB[1] = _g; + strokeRGB[2] = _b; + } + + public void setImage(IImage image_) { + image = image_; + } + + public void setTopEllipsesImage(IImage image_) { + ellipsesImage = image_; + } + + /** + * Set the time when the execution occurrence starts.
+ * + * @param time the time when the execution occurrence starts + */ + public void setStartTime(TmfTimestamp time) { + startTime = time.clone(); + hasTime = true; + if (lifeline != null) { + lifeline.setTimeInfo(true); + } + } + + /** + * Set the time when the execution occurrence ends.
+ * + * @param time the time when the execution occurrence ends + */ + public void setEndTime(TmfTimestamp time) { + endTime = time.clone(); + hasTime = true; + if (lifeline != null) { + lifeline.setTimeInfo(true); + } + } + + /** + * Returns the time when the execution occurrence starts + * + * @return the time + */ + @Override + public TmfTimestamp getStartTime() { + return startTime; + } + + /** + * Returns the time when the execution occurrence ends + * + * @return the time + */ + @Override + public TmfTimestamp getEndTime() { + return endTime; + } + + @Override + public boolean hasTimeInfo() { + return hasTime; + } + + @Override + public void draw(IGC context) { + super.draw(context); + int x = getX(); + int y = getY(); + int width = getWidth(); + int height = getHeight(); + if (image != null) { + context.drawImage(image, x + width - 4, y + height - 11, 8, 11); + } + if (ellipsesImage != null) { + context.drawImage(ellipsesImage, x + width, y, 40, 10); + } + } + + /** + * Extension in order to support customized fill colors + * + * @param context + * @return IColor + */ + @Override + protected IColor setUnselectedFillColor(IGC context) { + if (fillRGB != null) { + IColor tempFillColor = context.createColor(fillRGB[0], fillRGB[1], fillRGB[2]); + if (Frame.getUserPref().useGradienColor()) { + context.setGradientColor(tempFillColor); + context.setForeground(Frame.getUserPref().getForeGroundColor(ISDPreferences.PREF_EXEC)); + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_FRAME)); + } else + context.setBackground(tempFillColor); + return tempFillColor; + } else { + return super.setUnselectedFillColor(context); + } + } + + /** + * Extension in order to support customized stroke colors + * + * @param context + * @return IColor + */ + @Override + protected IColor setUnselectedStrokeColor(IGC context) { + if (strokeRGB != null) { + IColor tempStrokeColor = context.createColor(strokeRGB[0], strokeRGB[1], strokeRGB[2]); + context.setForeground(tempStrokeColor); + return tempStrokeColor; + } else { + return super.setUnselectedStrokeColor(context); + } + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Frame.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Frame.java new file mode 100755 index 0000000000..c15cc12b55 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Frame.java @@ -0,0 +1,921 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: Frame.java,v 1.3 2008/01/24 02:28:49 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IColor; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.preferences.SDViewPref; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.TimeEventComparator; + +/** + * The Frame class is the base sequence diagram graph nodes container.
+ * For instance, only one frame can be drawn in the View.
+ * Lifelines, Messages and Stop which are supposed to represent a Sequence diagram are drawn in a Frame.
+ * Only the graph node added to their representing list will be drawn. + * + * The lifelines are appended along the X axsis when added in a frame.
+ * The syncMessages are ordered along the Y axsis depending on the event occurrence they are attached to.
+ * + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline Lifeline for more event occurence details + * @author sveyrier + * @version 1.0 + */ +public class Frame extends BasicFrame { + + protected Lifeline highlightLifeline = null; + protected int startEvent = 0; + protected int nbEvent = 0; + protected IColor highlightColor = null; + + protected List executionOccurrencesWithTime; + + protected LifelineCategories[] lifelineCategories = null; + + /** + * Returns a list of all lifelines known by this frame. Known lifelines are the only one which can be displayed on + * screen. + * + * @return the lifelines list + */ + protected List getLifelines() { + if (!hasChilden) + return null; + else + return (List) nodes.get(Lifeline.LIFELINE_TAG); + } + + /** + * Returns the number of lifelines stored in the frame + * + * @return the number of lifelines + */ + public int lifeLinesCount() { + List lifelines = getLifelines(); + if (lifelines != null) + return lifelines.size(); + else + return 0; + } + + /** + * Returns the lifeline at the given index in the lifelines array + * + * @param index the position in the lifeline array + * @return the lifeline + */ + public Lifeline getLifeline(int index) { + if ((getLifelines() != null) && (index >= 0) && (index < lifeLinesCount())) + return (Lifeline) getLifelines().get(index); + return null; + } + + /** + * Returns a list of syncMessages known by this frame. Known syncMessages are the only on which can be displayed on + * screen + * + * @return the syncMessages list + */ + protected List getSyncMessages() { + if (!hasChilden) + return null; + else + return (List) nodes.get(SyncMessage.SYNC_MESS_TAG); + } + + /** + * Returns the number of syncMessages stored in the frame + * + * @return the number of syncMessage + */ + public int syncMessageCount() { + if (getSyncMessages() != null) + return getSyncMessages().size(); + else + return 0; + } + + /** + * Returns the syncMessage at the given index in the syncMessages array + * + * @param index the position in the syncMessages array + * @return the syncMessage + */ + public SyncMessage getSyncMessage(int index) { + if ((getSyncMessages() != null) && (index >= 0) && (index < getSyncMessages().size())) + return (SyncMessage) getSyncMessages().get(index); + return null; + } + + /** + * Returns a list of asyncMessages known by this frame. Known asyncMessages are the only on which can be displayed + * on screen + * + * @return the asyncMessages list + */ + protected List getAsyncMessages() { + if (!hasChilden) + return null; + else + return (List) nodes.get(AsyncMessage.ASYNC_MESS_TAG); + } + + /** + * Returns the number of asyncMessage stored in the frame + * + * @return the number of asyncMessage + */ + public int asyncMessageCount() { + if (getAsyncMessages() != null) + return getAsyncMessages().size(); + else + return 0; + } + + /** + * Returns the asyncMessage at the given index in the asyncMessage array + * + * @param index the position in the asyncMessage array + * @return the asyncMessage + */ + public AsyncMessage getAsyncMessage(int index) { + if ((getAsyncMessages() != null) && (index >= 0) && (index < getAsyncMessages().size())) + return (AsyncMessage) getAsyncMessages().get(index); + return null; + } + + /** + * Returns a list of syncMessages return known by this frame. Known syncMessages return are the only on which can be + * displayed on screen + * + * @return the syncMessages return list + */ + protected List getSyncMessagesReturn() { + if (!hasChilden) + return null; + else + return (List) nodes.get(SyncMessageReturn.SYNC_MESS_RET_TAG); + } + + /** + * Returns the number of syncMessageReturn stored in the frame + * + * @return the number of syncMessageReturn + */ + public int syncMessageReturnCount() { + if (getSyncMessagesReturn() != null) + return getSyncMessagesReturn().size(); + else + return 0; + } + + /** + * Returns the syncMessageReturn at the given index in the syncMessageReturn array + * + * @param index the position in the syncMessageReturn array + * @return the syncMessageReturn + */ + public SyncMessageReturn getSyncMessageReturn(int index) { + if ((getSyncMessagesReturn() != null) && (index >= 0) && (index < getSyncMessagesReturn().size())) + return (SyncMessageReturn) getSyncMessagesReturn().get(index); + return null; + } + + /** + * Returns a list of asyncMessageRetun known by this frame. Known asyncMessageRetun are the only on which can be + * displayed on screen + * + * @return the asyncMessageRetun list + */ + protected List getAsyncMessagesReturn() { + if (!hasChilden) + return null; + else + return (List) nodes.get(AsyncMessageReturn.ASYNC_MESS_RET_TAG); + } + + /** + * Returns the number of asyncMessageReturn stored in the frame + * + * @return the number of asyncMessageReturn + */ + public int asyncMessageReturnCount() { + if (getAsyncMessagesReturn() != null) + return getAsyncMessagesReturn().size(); + else + return 0; + } + + /** + * Returns the asyncMessageReturn at the given index in the asyncMessageReturn array + * + * @param index the position in the asyncMessageReturn array + * @return the asyncMessageReturn + */ + public AsyncMessageReturn getAsyncMessageReturn(int index) { + if ((getAsyncMessagesReturn() != null) && (index >= 0) && (index < getAsyncMessagesReturn().size())) + return (AsyncMessageReturn) getAsyncMessagesReturn().get(index); + return null; + } + + /** + * Adds a lifeline to the frame lifelines list. The lifeline X drawing order depends on the lifeline addition order + * into the frame lifelines list. + * + * @param the lifeline to add + */ + public void addLifeLine(Lifeline lifeLine) { + computeMinMax = true; + if (lifeLine == null) + return; + // set the lifeline parent frame + lifeLine.setFrame(this); + // Increate the frame lifeline counter + // and set the lifeline drawing order + lifeLine.setIndex(getNewHorizontalIndex()); + if (lifeLine.hasTimeInfo()) { + timeInfo = true; + } + // add the lifeline to the lifelines list + addNode(lifeLine); + } + + /** + * Returns the first visible lifeline drawn in the view + * + * @return the first visible lifeline index + */ + public int getFirstVisibleLifeline() { + if (!hasChilden) + return 0; + else if (indexes.get(Lifeline.LIFELINE_TAG) != null) + return ((Integer) indexes.get(Lifeline.LIFELINE_TAG)).intValue(); + else + return 0; + } + + /** + * Returns the first visible synchronous message drawn in the view + * + * @return the first visible synchronous message index + */ + public int getFirstVisibleSyncMessage() { + if (!hasChilden) + return 0; + else if (indexes.get(SyncMessage.SYNC_MESS_TAG) != null) + return ((Integer) indexes.get(SyncMessage.SYNC_MESS_TAG)).intValue(); + else + return 0; + } + + /** + * Returns the first visible synchronous message return drawn in the view + * + * @return the first visible synchronous message return index + */ + public int getFirstVisibleSyncMessageReturn() { + if (!hasChilden) + return 0; + else if (indexes.get(SyncMessageReturn.SYNC_MESS_RET_TAG) != null) + return ((Integer) indexes.get(SyncMessageReturn.SYNC_MESS_RET_TAG)).intValue(); + else + return 0; + } + + /** + * Returns the first visible synchronous message drawn in the view + * + * @return the first visible synchronous message index + */ + public int getFirstVisibleAsyncMessage() { + if (!hasChilden) + return 0; + else if (indexes.get(AsyncMessage.ASYNC_MESS_TAG) != null) + return ((Integer) indexes.get(AsyncMessage.ASYNC_MESS_TAG)).intValue(); + else + return 0; + } + + /** + * Returns the first visible synchronous message return drawn in the view + * + * @return the first visible synchronous message return index + */ + public int getFirstVisibleAsyncMessageReturn() { + if (!hasChilden) + return 0; + else if (indexes.get(AsyncMessageReturn.ASYNC_MESS_RET_TAG) != null) + return ((Integer) indexes.get(AsyncMessageReturn.ASYNC_MESS_RET_TAG)).intValue(); + else + return 0; + } + + public List getExecutionOccurrencesWithTime() { + return executionOccurrencesWithTime; + } + + public void insertLifelineAfter(Lifeline toInsert, Lifeline after) { + if ((toInsert == null)) + return; + if (toInsert == after) + return; + int insertPoint = 0; + if (after != null) + insertPoint = after.getIndex(); + int removePoint = toInsert.getIndex() - 1; + if (removePoint >= insertPoint) + getLifelines().remove(removePoint); + getLifelines().add(insertPoint, toInsert); + if (removePoint < insertPoint) + getLifelines().remove(removePoint); + + if (removePoint >= insertPoint) + toInsert.setIndex(insertPoint + 1); + else + toInsert.setIndex(insertPoint - 1); + + insertPoint++; + if (removePoint >= insertPoint) { + for (int i = insertPoint; i < getLifelines().size(); i++) { + getLifeline(i).setIndex(i + 1); + } + } else { + for (int i = 0; i < insertPoint && i < getLifelines().size(); i++) { + getLifeline(i).setIndex(i + 1); + } + } + } + + public void insertLifelineBefore(Lifeline toInsert, Lifeline before) { + if ((toInsert == null)) + return; + if (toInsert == before) + return; + int insertPoint = 0; + if (before != null) + insertPoint = before.getIndex() - 1; + int removePoint = toInsert.getIndex() - 1; + if (removePoint >= insertPoint) + getLifelines().remove(removePoint); + getLifelines().add(insertPoint, toInsert); + if (removePoint < insertPoint) + getLifelines().remove(removePoint); + + if (removePoint >= insertPoint) + toInsert.setIndex(insertPoint + 1); + else + toInsert.setIndex(insertPoint - 1); + + insertPoint++; + if (removePoint >= insertPoint) { + for (int i = insertPoint; i < getLifelines().size(); i++) { + getLifeline(i).setIndex(i + 1); + } + } else { + for (int i = 0; i < insertPoint && i < getLifelines().size(); i++) { + getLifeline(i).setIndex(i + 1); + } + } + } + + public Lifeline getCloserLifeline(int x) { + int index = (x - Metrics.FRAME_H_MARGIN + Metrics.LIFELINE_H_MAGIN) / Metrics.swimmingLaneWidth() - 1; + if (index < 0) + index = 0; + if (index >= getLifelines().size()) + index = getLifelines().size() - 1; + Lifeline node1, node2, node3; + int dist1, dist2, dist3; + node1 = node2 = node3 = getLifeline(index); + dist1 = dist2 = dist3 = Math.abs(node1.getX() + node1.getWidth() / 2 - x); + if (index > 0) { + node2 = getLifeline(index - 1); + dist2 = Math.abs(node2.getX() + node2.getWidth() / 2 - x); + } + if (index < getLifelines().size() - 1) { + node3 = getLifeline(index + 1); + dist3 = Math.abs(node3.getX() + node3.getWidth() / 2 - x); + } + if (dist1 <= dist2 && dist1 <= dist3) + return node1; + else if (dist2 <= dist1 && dist2 <= dist3) + return node2; + else + return node3; + } + + public void reorder(ArrayList list) { + for (int i = 0; i < list.size(); i++) { + if (list.get(i) instanceof Lifeline[]) { + Lifeline temp[] = (Lifeline[]) list.get(i); + if (temp.length == 2) { + if (temp[1] == null) { + insertLifelineAfter(temp[0], getLifeline(lifeLinesCount() - 1)); + } else + insertLifelineBefore(temp[0], temp[1]); + } + } + } + } + + public void resetTimeCompression() { + highlightLifeline = null; + this.startEvent = 0; + this.nbEvent = 0; + highlightColor = null; + } + + @Override + protected void computeMinMax() { + List timeArray = buildTimeArray(); + if (timeArray == null) + return; + for (int i = 0; i < timeArray.size() - 1; i++) { + SDTimeEvent m1 = (SDTimeEvent) timeArray.get(i); + SDTimeEvent m2 = (SDTimeEvent) timeArray.get(i + 1); + if (SDViewPref.getInstance().excludeExternalTime()) + if ((m1.getGraphNode() instanceof BaseMessage) && (m2.getGraphNode() instanceof BaseMessage)) { + BaseMessage mes1 = (BaseMessage) m1.getGraphNode(); + BaseMessage mes2 = (BaseMessage) m2.getGraphNode(); + if ((mes2.startLifeline == null) || (mes1.endLifeline == null)) + continue; + } + + updateMinMax(m1, m2); + } + } + + /** + * Find the two graph nodes that are closest to this date, one just earlier, second just later. If date is before + * any graph node, bounds[0] is null and bounds[1] is the earliest. If date is after any graph node, bounds[1] is + * null and bounds[0] is the latest. + * + * @param dateToFind date to be found + * @param bounds a two items array that will receive bounds if found + * @return true if both bounds not null + */ + public boolean findDateBounds(TmfTimestamp dateToFind, ITimeRange bounds[]) { + if (hasTimeInfo()) { + List timeArray = buildTimeArray(); + bounds[0] = null; + bounds[1] = null; + for (int i = 0; i < timeArray.size(); i++) { + SDTimeEvent m = (SDTimeEvent) timeArray.get(i); + if (m.getTime().compareTo(dateToFind, true) > 0) { + bounds[1] = m.getGraphNode(); + if (i > 0) { + bounds[0] = ((SDTimeEvent) timeArray.get(i - 1)).getGraphNode(); + return true; + } + return false; + } + } + bounds[0] = ((SDTimeEvent) timeArray.get(timeArray.size() - 1)).getGraphNode(); + } + return false; + } + + protected void setHasTimeInfo(boolean value) { + timeInfo = value; + } + + /** + * @return true if frame has time info else false + */ + public boolean hasTimeInfo() { + return timeInfo; + } + + /** + * @param lifeline + * @param startEvent + * @param nbEvent + * @param color + */ + public void highlightTimeCompression(Lifeline lifeline, int startEvent, int nbEvent, IColor color) { + highlightLifeline = lifeline; + this.startEvent = startEvent; + this.nbEvent = nbEvent; + highlightColor = color; + } + + /** + * Set the lifeline categories which will be use during the lifelines creation + * + * @see Lifeline#setCategory(int) + * @param categories the lifeline categories array + */ + public void setLifelineCategories(LifelineCategories[] categories) { + lifelineCategories = categories; + } + + /** + * Returns the lifeline categories array set for the this frame + * + * @return the lifeline categories array or null if not set + */ + public LifelineCategories[] getLifelineCategories() { + return lifelineCategories; + } + + /** + * Adds a message to the Frame message list. Four kinds of syncMessages can be added:
+ * - synchronous syncMessages
+ * - synchronous syncMessages return
+ * - asynchronous syncMessages
+ * - asynchronous syncMessages return
+ * For drawing performance reason, it is recommended to add synchronous syncMessages in the same order they should + * appear along the Y axis in the Frame. + * + * @param the message to add + */ + public void addMessage(BaseMessage message) { + addNode(message); + } + + @Override + public void draw(IGC context) { + drawFrame(context); + if (!hasChilden) + return; + + if (highlightLifeline != null) { + IColor backupColor = context.getBackground(); + context.setBackground(Frame.getUserPref().getTimeCompressionSelectionColor()); + int gy = highlightLifeline.getY() + highlightLifeline.getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * startEvent; + context.fillRectangle(Metrics.FRAME_H_MARGIN + 1, gy, highlightLifeline.getX() + Metrics.getLifelineWidth() / 2 - Metrics.FRAME_H_MARGIN, (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * nbEvent); + context.setBackground(backupColor); + } + super.draw(context, false); + int lifelineArryStep = 1; + if (Metrics.swimmingLaneWidth() * context.getZoom() < Metrics.LIFELINE_SIGNIFICANT_HSPACING) + lifelineArryStep = Math.round(Metrics.LIFELINE_SIGNIFICANT_HSPACING / (Metrics.swimmingLaneWidth() * context.getZoom())); + if (indexes.size() == 0) + return; + int lifeLineDrawIndex = ((Integer) indexes.get(Lifeline.LIFELINE_TAG)).intValue(); + for (int i = lifeLineDrawIndex; i < ((List) nodes.get(Lifeline.LIFELINE_TAG)).size(); i = i + lifelineArryStep) { + Lifeline toDraw = (Lifeline) ((List) nodes.get(Lifeline.LIFELINE_TAG)).get(i); + if (toDraw.getX() - Metrics.LIFELINE_SPACING / 2 > context.getContentsX() + context.getVisibleWidth()) + break; + toDraw.drawName(context); + + if (highlightLifeline != null) { + if (toDraw == highlightLifeline) + toDraw.highlightExecOccurrenceRegion(context, startEvent, nbEvent, highlightColor); + else if ((toDraw.getIndex() < highlightLifeline.getIndex()) || ((toDraw.getIndex() < highlightLifeline.getIndex()))) { + + int acIndex = toDraw.getExecOccurrenceDrawIndex(); + // acIndex = first visible execution occurrence + // for drawing speed reason with only search on the visible subset + if (toDraw.getExecutions() != null) + for (int index = acIndex; index < toDraw.getExecutions().size(); index++) { + BasicExecutionOccurrence exec = (BasicExecutionOccurrence) toDraw.getExecutions().get(index); + int tempEvent = startEvent; + for (int j = 0; j < nbEvent; j++) { + if (((tempEvent >= exec.startEventOccurrence) && (tempEvent <= exec.endEventOccurrence) && (tempEvent + 1 >= exec.startEventOccurrence) && (tempEvent + 1 <= exec.endEventOccurrence))) { + toDraw.highlightExecOccurrenceRegion(context, tempEvent, 1, Frame.getUserPref().getTimeCompressionSelectionColor()); + } + tempEvent = tempEvent + 1; + } + // if we are outside the visible area we stop right now + // This works because execution occurrences are ordered along the Y axis + if (exec.getY() > getY()) + break; + } + } + } + } + } + + @Override + protected List buildTimeArray() { + if (!hasChilden) + return null; + try { + List timeArray = super.buildTimeArray(); + executionOccurrencesWithTime = null; + if (getLifelines() != null) + for (int i = 0; i < ((List) nodes.get(Lifeline.LIFELINE_TAG)).size(); i++) { + Lifeline l = (Lifeline) ((List) nodes.get(Lifeline.LIFELINE_TAG)).get(i); + if (l.hasTimeInfo() && l.getExecutions() != null) { + for (Iterator j = l.getExecutions().iterator(); j.hasNext();) { + GraphNode o = j.next(); + if (o instanceof ExecutionOccurrence) { + ExecutionOccurrence eo = (ExecutionOccurrence) o; + if (eo.hasTimeInfo()) { + int event = eo.getStartOccurrence(); + TmfTimestamp time = eo.getStartTime(); + SDTimeEvent f = new SDTimeEvent(time, event, eo); + timeArray.add(f); + if (executionOccurrencesWithTime == null) { + executionOccurrencesWithTime = new ArrayList(); + } + executionOccurrencesWithTime.add(f); + event = eo.getEndOccurrence(); + time = eo.getEndTime(); + f = new SDTimeEvent(time, event, eo); + timeArray.add(f); + executionOccurrencesWithTime.add(f); + } + } + } + } + } + + if (executionOccurrencesWithTime != null) { + SDTimeEvent[] temp = executionOccurrencesWithTime.toArray(new SDTimeEvent[0]); + Arrays.sort(temp, new TimeEventComparator()); + executionOccurrencesWithTime = Arrays.asList(temp); + } + SDTimeEvent[] temp = timeArray.toArray(new SDTimeEvent[0]); + Arrays.sort(temp, new TimeEventComparator()); + timeArray = Arrays.asList(temp); + return timeArray; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + } + + protected GraphNode getCloserLeavingMessage(Lifeline lifeline, BaseMessage message, List list, boolean smallerEvent) { + if (list == null) + return null; + if (smallerEvent == false) { + int event = 0; + if (message != null) + event = message.getEventOccurrence(); + for (int i = 0; i < list.size(); i++) { + GraphNode node = (GraphNode) list.get(i); + if (node instanceof SyncMessage) { + SyncMessage syncNode = (SyncMessage) node; + if ((syncNode.getEventOccurrence() > event) && (syncNode.getStartLifeline() == lifeline) && !syncNode.isSameAs(message)) + return node; + } else if (node instanceof AsyncMessage) { + AsyncMessage asyncNode = (AsyncMessage) node; + if ((asyncNode.getStartOccurrence() > event) && (asyncNode.getStartLifeline() == lifeline) && !asyncNode.isSameAs(message)) + return node; + } + } + } else { + int event = getMaxEventOccurrence(); + if (message != null) + if (message instanceof AsyncMessage) { + event = ((AsyncMessage) message).getStartOccurrence(); + } else + event = message.getEventOccurrence(); + for (int i = list.size() - 1; i >= 0; i--) { + GraphNode node = (GraphNode) list.get(i); + if (node instanceof SyncMessage) { + SyncMessage syncNode = (SyncMessage) node; + if ((syncNode.getEventOccurrence() < event) && (syncNode.getStartLifeline() == lifeline) && !syncNode.isSameAs(message)) + return node; + } else if (node instanceof AsyncMessage) { + AsyncMessage asyncNode = (AsyncMessage) node; + if ((asyncNode.getStartOccurrence() < event) && (asyncNode.getStartLifeline() == lifeline) && !asyncNode.isSameAs(message)) + return node; + } + } + } + return null; + } + + protected GraphNode getCloserEnteringMessage(Lifeline lifeline, BaseMessage message, List list, boolean smallerEvent) { + if (list == null) + return null; + if (smallerEvent == false) { + int event = 0; + if (message != null) + event = message.getEventOccurrence(); + for (int i = 0; i < list.size(); i++) { + GraphNode node = (GraphNode) list.get(i); + if (node instanceof SyncMessage) { + SyncMessage syncNode = (SyncMessage) node; + if ((syncNode.getEventOccurrence() > event) && (syncNode.getEndLifeline() == lifeline) && !syncNode.isSameAs(message)) + return node; + } else if (node instanceof AsyncMessage) { + AsyncMessage asyncNode = (AsyncMessage) node; + if ((asyncNode.getStartOccurrence() > event) && (asyncNode.getEndLifeline() == lifeline) && !asyncNode.isSameAs(message)) + return node; + } + } + } else { + int event = getMaxEventOccurrence(); + if (message != null) + if (message instanceof AsyncMessage) { + event = ((AsyncMessage) message).getStartOccurrence(); + } else + event = message.getEventOccurrence(); + for (int i = list.size() - 1; i >= 0; i--) { + GraphNode node = (GraphNode) list.get(i); + if (node instanceof SyncMessage) { + SyncMessage syncNode = (SyncMessage) node; + if ((syncNode.getEventOccurrence() < event) && (syncNode.getEndLifeline() == lifeline) && !syncNode.isSameAs(message)) + return node; + } else if (node instanceof AsyncMessage) { + AsyncMessage asyncNode = (AsyncMessage) node; + if ((asyncNode.getStartOccurrence() < event) && (asyncNode.getEndLifeline() == lifeline) && !asyncNode.isSameAs(message)) + return node; + } + } + } + return null; + } + + protected int distanceFromEvent(GraphNode node, int event) { + int distance = 0; + if (node instanceof SyncMessage) + distance = ((SyncMessage) node).getEventOccurrence() - event; + else if (node instanceof AsyncMessage) { + int start = ((AsyncMessage) node).getStartOccurrence(); + int end = ((AsyncMessage) node).getEndOccurrence(); + if ((start - event) < (end - event)) + distance = start - event; + else + distance = end - event; + } + return Math.abs(distance); + } + + protected GraphNode getCloserToEvent(GraphNode node1, GraphNode node2, int event) { + if ((node1 != null) && (node2 != null)) { + if (distanceFromEvent(node1, event) < distanceFromEvent(node2, event)) + return node1; + else + return node2; + } else if (node1 != null) + return node1; + else if (node2 != null) + return node2; + else + return null; + } + + public GraphNode getCalledMessage(BaseMessage StartMessage) { + int event = 0; + GraphNode result = null; + Lifeline lifeline = null; + if (StartMessage != null) { + event = ((BaseMessage) StartMessage).getEventOccurrence(); + lifeline = ((BaseMessage) StartMessage).getEndLifeline(); + if (lifeline == null) + lifeline = ((BaseMessage) StartMessage).getStartLifeline(); + } + if (lifeline == null) + return null; + GraphNode message = getCloserLeavingMessage(lifeline, StartMessage, getSyncMessages(), false); + GraphNode messageReturn = getCloserLeavingMessage(lifeline, StartMessage, getSyncMessagesReturn(), false); + result = getCloserToEvent(message, messageReturn, event); + message = getCloserLeavingMessage(lifeline, StartMessage, getAsyncMessages(), false); + result = getCloserToEvent(result, message, event); + messageReturn = getCloserLeavingMessage(lifeline, StartMessage, getAsyncMessagesReturn(), false); + result = getCloserToEvent(result, messageReturn, event); + return result; + } + + public GraphNode getCallerMessage(BaseMessage StartMessage) { + int event = getMaxEventOccurrence(); + GraphNode result = null; + Lifeline lifeline = null; + if (StartMessage != null) { + event = ((BaseMessage) StartMessage).getEventOccurrence(); + lifeline = ((BaseMessage) StartMessage).getStartLifeline(); + if (lifeline == null) + lifeline = ((BaseMessage) StartMessage).getEndLifeline(); + } + if (lifeline == null) + return null; + GraphNode message = getCloserEnteringMessage(lifeline, StartMessage, getSyncMessages(), true); + GraphNode messageReturn = getCloserEnteringMessage(lifeline, StartMessage, getSyncMessagesReturn(), true); + result = getCloserToEvent(message, messageReturn, event); + message = getCloserEnteringMessage(lifeline, StartMessage, getAsyncMessages(), true); + result = getCloserToEvent(result, message, event); + messageReturn = getCloserEnteringMessage(lifeline, StartMessage, getAsyncMessagesReturn(), true); + result = getCloserToEvent(result, messageReturn, event); + return result; + } + + public GraphNode getNextLifelineMessage(Lifeline lifeline, BaseMessage StartMessage) { + int event = 0; + if (StartMessage != null) + event = ((BaseMessage) StartMessage).getEventOccurrence(); + if (lifeline == null) + return null; + GraphNode message = getCloserLeavingMessage(lifeline, StartMessage, getSyncMessages(), false); + GraphNode messageReturn = getCloserLeavingMessage(lifeline, StartMessage, getSyncMessagesReturn(), false); + GraphNode result = getCloserToEvent(message, messageReturn, event); + message = getCloserLeavingMessage(lifeline, StartMessage, getAsyncMessages(), false); + result = getCloserToEvent(result, message, event); + messageReturn = getCloserLeavingMessage(lifeline, StartMessage, getAsyncMessagesReturn(), false); + result = getCloserToEvent(result, messageReturn, event); + return result; + } + + public BasicExecutionOccurrence getFirstExecution(Lifeline lifeline) { + if (lifeline == null) + return null; + List list = lifeline.getExecutions(); + if (list == null) + return null; + if (list.size() == 0) + return null; + BasicExecutionOccurrence result = (BasicExecutionOccurrence) list.get(0); + for (int i = 0; i < list.size(); i++) { + BasicExecutionOccurrence e = (BasicExecutionOccurrence) list.get(i); + if ((e.getStartOccurrence() < result.getEndOccurrence())) + result = e; + } + return result; + } + + public BasicExecutionOccurrence getPrevExecOccurrence(BasicExecutionOccurrence exec) { + if (exec == null) + return null; + Lifeline lifeline = exec.getLifeline(); + if (lifeline == null) + return null; + List list = lifeline.getExecutions(); + if (list == null) + return null; + BasicExecutionOccurrence result = null; + for (int i = 0; i < list.size(); i++) { + BasicExecutionOccurrence e = (BasicExecutionOccurrence) list.get(i); + if ((e.getStartOccurrence() < exec.startEventOccurrence) && (result == null)) + result = e; + if ((e.getStartOccurrence() < exec.startEventOccurrence) && (e.getStartOccurrence() >= result.getEndOccurrence())) + result = e; + } + return result; + } + + public BasicExecutionOccurrence getNextExecOccurrence(BasicExecutionOccurrence exec) { + if (exec == null) + return null; + Lifeline lifeline = exec.getLifeline(); + if (lifeline == null) + return null; + List list = lifeline.getExecutions(); + if (list == null) + return null; + BasicExecutionOccurrence result = null; + for (int i = 0; i < list.size(); i++) { + BasicExecutionOccurrence e = (BasicExecutionOccurrence) list.get(i); + if ((e.getStartOccurrence() > exec.startEventOccurrence) && (result == null)) + result = e; + if ((e.getStartOccurrence() > exec.startEventOccurrence) && (e.getStartOccurrence() <= result.getEndOccurrence())) + result = e; + } + return result; + } + + public BasicExecutionOccurrence getLastExecOccurrence(Lifeline lifeline) { + if (lifeline == null) + return null; + List list = lifeline.getExecutions(); + if (list == null) + return null; + BasicExecutionOccurrence result = null; + for (int i = 0; i < list.size(); i++) { + BasicExecutionOccurrence e = (BasicExecutionOccurrence) list.get(i); + if (result == null) + result = e; + if (e.getStartOccurrence() > result.getEndOccurrence()) + result = e; + } + return result; + } + + public GraphNode getPrevLifelineMessage(Lifeline lifeline, BaseMessage StartMessage) { + int event = getMaxEventOccurrence(); + if (StartMessage != null) + if (StartMessage instanceof AsyncMessage) { + event = ((AsyncMessage) StartMessage).getStartOccurrence(); + } else + event = StartMessage.getEventOccurrence(); + if (lifeline == null) + return null; + GraphNode message = getCloserLeavingMessage(lifeline, StartMessage, getSyncMessages(), true); + GraphNode messageReturn = getCloserLeavingMessage(lifeline, StartMessage, getSyncMessagesReturn(), true); + GraphNode result = getCloserToEvent(message, messageReturn, event); + message = getCloserLeavingMessage(lifeline, StartMessage, getAsyncMessages(), true); + result = getCloserToEvent(result, message, event); + messageReturn = getCloserLeavingMessage(lifeline, StartMessage, getAsyncMessagesReturn(), true); + result = getCloserToEvent(result, messageReturn, event); + return result; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/GraphNode.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/GraphNode.java new file mode 100755 index 0000000000..e719f99e96 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/GraphNode.java @@ -0,0 +1,713 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: GraphNode.java,v 1.3 2008/01/24 02:28:49 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.linuxtools.tmf.ui.TmfUiTracer; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; + +/** + * The base class used for all UML2 graph nodes displayed in the Sequence Diagram SDWidget. + * + * @author sveyrier + * @version 1.0 + */ + +public abstract class GraphNode { + + protected int startEventOccurrence = 0; + + protected int endEventOccurrence = 0; + + /** + * Preference ColorId to use to draw font + */ + public String prefId = ISDPreferences.PREF_SYNC_MESS; + + /** + * The selection state of the graph node. + */ + protected boolean selected = false; + + /** + * The focus state of the graph node. + */ + protected boolean focused = false; + + protected boolean hasChilden = false; + + /** + * The graph node name used to label the graph node in the View. + */ + protected String name = ""; //$NON-NLS-1$ + + protected HashMap> nodes; + protected HashMap> fnodes; + protected HashMap> bnodes; + + protected HashMap indexes; + protected HashMap fSort; + protected HashMap bSort; + + /** + * Reset the internal index of the first visible GraphNode for each ordered GraphNode lists + * + */ + public void resetIndex() { + if (!hasChilden) + return; + Iterator it = indexes.keySet().iterator(); + while (it.hasNext()) { + String nodeType = it.next(); + indexes.put(nodeType, Integer.valueOf(0)); + } + } + + /** + * Add a GraphNode into the receiver + * + * @param nodeToAdd the node to add + */ + public void addNode(GraphNode nodeToAdd) { + if (!hasChilden) { + nodes = new HashMap>(2); + fnodes = new HashMap>(2); + bnodes = new HashMap>(2); + indexes = new HashMap(2); + bSort = new HashMap(2); + fSort = new HashMap(2); + hasChilden = true; + } + + // Nothing to add + if (nodeToAdd == null) + return; + + if (nodes.get(nodeToAdd.getArrayId()) == null) { + nodes.put(nodeToAdd.getArrayId(), new ArrayList(1)); + indexes.put(nodeToAdd.getArrayId(), Integer.valueOf(0)); + fnodes.put(nodeToAdd.getArrayId(), new ArrayList(1)); + fSort.put(nodeToAdd.getArrayId(), Boolean.valueOf(false)); + if (nodeToAdd.getBackComparator() != null) { + bnodes.put(nodeToAdd.getArrayId(), new ArrayList(1)); + bSort.put(nodeToAdd.getArrayId(), Boolean.valueOf(false)); + } + } + + List fNodeList = (List) fnodes.get(nodeToAdd.getArrayId()); + List bNodeList = null; + if (bnodes != null) + bNodeList = (List) bnodes.get(nodeToAdd.getArrayId()); + if (fNodeList != null && fNodeList.size() > 0) { + // check if the nodes are added y ordered + // if not, tag the list to sort it later (during draw) + GraphNode node = (GraphNode) fNodeList.get(fNodeList.size() - 1); + Comparator fcomp = nodeToAdd.getComparator(); + Comparator bcomp = nodeToAdd.getBackComparator(); + if ((fcomp != null) && (fcomp.compare(node, nodeToAdd) == 1)) { + fSort.put(nodeToAdd.getArrayId(), Boolean.valueOf(true)); + } + if ((bcomp != null) && (bcomp.compare(node, nodeToAdd) == 1)) { + bSort.put(nodeToAdd.getArrayId(), Boolean.valueOf(true)); + } + } + + if (fNodeList == null) { + fNodeList = new ArrayList(); + } + + fNodeList.add(nodeToAdd); + nodes.put(nodeToAdd.getArrayId(), fNodeList); + fnodes.put(nodeToAdd.getArrayId(), fNodeList); + if (nodeToAdd.getBackComparator() != null) { + bNodeList.add(nodeToAdd); + bnodes.put(nodeToAdd.getArrayId(), bNodeList); + } + } + + /** + * Set the graph node name.
+ * It is the name display in the view to label the graph node. + * + * @param the name to set + */ + public void setName(String nodeName) { + name = nodeName; + } + + /** + * Returns the graph node name.
+ * It is the name display in the view to label the graph node. + * + * @return the graph node name + */ + public String getName() { + return name; + } + + /** + * Tags the the graph node has selected.
+ * WARNING: This method is only used to draw the graph node using the system selection colors.
+ * To use the complete SDViewer selection mechanism (selection management, notification, etc..) see SDWidget class + * + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget#addSelection(GraphNode) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget#removeSelection(GraphNode) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget#clearSelection() + * @param selection - true to set selected, false to set unselected + */ + public void setSelected(boolean selection) { + selected = selection; + } + + /** + * Tags the the graph node as focused.
+ * WARNING: This method is only used to draw the graph node using the system focus style.
+ * To use the complete SDViewer focus mechanism see SDWidget class + * + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget#addSelection(GraphNode) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget#removeSelection(GraphNode) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget#clearSelection() + * @param focus - true to set focued, false otherwise + */ + public void setFocused(boolean focus) { + focused = focus; + } + + /** + * Returns true if the graph node is selected, false otherwise.
+ * The returned value is used to highlight the graph node in the View. + * + * @return true if selected, false otherwise + * + */ + public boolean isSelected() { + return selected; + } + + /** + * Returns true if the graph node is focused, false otherwise.
+ * The returned value is used to highlight the graph node in the View. + * + * @return true if focued, false otherwise + * + */ + public boolean hasFocus() { + return focused; + } + + /** + * Returns true if the graph node contains the point given in parameter, return false otherwise. + * + * @param x the x coordinate of the point to test containment
+ * y the y coordinate of the point to test containment + * @return true if contained, false otherwise + */ + abstract public boolean contains(int x, int y); + + /** + * Returns the x coordinate of the graph node + * + * @return the x coordinate + */ + abstract public int getX(); + + /** + * Returns the y coordinate of the graph node + * + * @return the y coordinate + */ + abstract public int getY(); + + /** + * Returns the graph node height + * + * @return the graph node height + */ + abstract public int getHeight(); + + /** + * Returns the graph node width + * + * @return the graph node width + */ + abstract public int getWidth(); + + /** + * Draws the graph node in the given context + * + * @param context the graphical context to draw in + */ + abstract protected void draw(IGC context); + + /** + * Returns the GraphNode visibility for the given visible area. Wrong visibility calculation, may strongly impact + * drawing performance + * + * @param vx + * @param vy + * @param vwidth + * @param vheight + * @return true if visible false otherwise + */ + public boolean isVisible(int x, int y, int width, int height) { + return true; + } + + /** + * Return a comparator to sort the GraphNode of the same type This comparator is used to order the GraphNode array + * of the given node type. (see getArrayId). + * + * @return the comparator + */ + public Comparator getComparator() { + return null; + } + + /** + * If needed, return a different comparator to backward scan the GraphNode array + * + * @return the backward comparator or null if not needed + */ + public Comparator getBackComparator() { + return null; + } + + /** + * Compare two graphNodes + * + * @param node the node to compare to + * @return true if equal false otherwise + */ + public boolean isSameAs(GraphNode node) { + return false; + } + + /** + * Return the node type for all class instances. This id is used to store the same nodes kind in the same ordered + * array. + * + * @return the node type identifier + */ + abstract public String getArrayId(); + + /** + * Return true if the distance from the GraphNode to the given point is positive + * + * @param x the point x coordinate + * @param y the point y coordinate + * @return true if positive false otherwise + */ + public boolean positiveDistanceToPoint(int x, int y) { + return false; + } + + /** + * Returns the graph node which contains the point given in parameter WARNING: Only graph nodes in the current + * visible area can be returned + * + * @param x the x coordinate of the point to test + * @param y the y coordinate of the point to test + * @return the graph node containing the point given in parameter, null otherwise + */ + public GraphNode getNodeAt(int x, int y) { + GraphNode toReturn = null; + + if (!hasChilden) + return null; + + Iterator it = nodes.keySet().iterator(); + GraphNode node = null; + while (it.hasNext()) { + Object nodeType = it.next(); + List list = (List) nodes.get(nodeType); + int index = ((Integer) indexes.get(nodeType)).intValue(); + node = getNodeFromListAt(x, y, list, index); + if (toReturn == null) + toReturn = node; + if (node != null) { + GraphNode internalNode = node.getNodeAt(x, y); + if (internalNode != null) + return internalNode; + // else return node; + else if (Math.abs(node.getWidth()) < Math.abs(toReturn.getWidth()) || Math.abs(node.getHeight()) < Math.abs(toReturn.getHeight())) + toReturn = node; + } + } + return toReturn; + } + + public ArrayList getNodeList(GraphNode from, GraphNode to) { + ArrayList result = new ArrayList(); + + if (from != null) { + result.add(from); + } else if (to != null) { + result.add(to); + } + + if (from == null || to == null) + return result; + + if (from == to) + return result; + + int startX = Math.min(from.getX(), Math.min(to.getX(), Math.min(from.getX() + from.getWidth(), to.getX() + to.getWidth()))); + int endX = Math.max(from.getX(), Math.max(to.getX(), Math.max(from.getX() + from.getWidth(), to.getX() + to.getWidth()))); + int startY = Math.min(from.getY(), Math.min(to.getY(), Math.min(from.getY() + from.getHeight(), to.getY() + to.getHeight()))); + int endY = Math.max(from.getY(), Math.max(to.getY(), Math.max(from.getY() + from.getHeight(), to.getY() + to.getHeight()))); + + if (!hasChilden) + return result; + + Iterator it = nodes.keySet().iterator(); + while (it.hasNext()) { + Object nodeType = it.next(); + List nodesList = (List) nodes.get(nodeType); + if (nodesList == null || nodesList.isEmpty()) + return null; + for (int i = 0; i < nodesList.size(); i++) { + GraphNode node = (GraphNode) nodesList.get(i); + int nw = node.getWidth(); + int nh = node.getHeight(); + int nx = node.getX(); + int ny = node.getY(); + if (contains(startX, startY, endX - startX, endY - startY, nx + 1, ny + 1) && contains(startX, startY, endX - startX, endY - startY, nx + nw - 2, ny + nh - 2)) + result.add(node); + result.addAll(node.getNodeList(from, to)); + } + } + if ((to != null) && (!result.contains(to))) + result.add(to); + return result; + } + + /** + * Returns the graph node which contains the point given in parameter for the given graph node list and starting the + * iteration at the given index
+ * WARNING: Only graph nodes with smaller coordinates than the current visible area can be returned.
+ * + * @param x the x coordinate of the point to test + * @param y the y coordinate of the point to test + * @param list the list to search in + * @param fromIndex list browsing starting point + * @return the graph node containing the point given in parameter, null otherwise + */ + protected GraphNode getNodeFromListAt(int x, int y, List list, int fromIndex) { + if (list == null) + return null; + for (int i = fromIndex; i < list.size(); i++) { + GraphNode node = (GraphNode) list.get(i); + if (node.contains(x, y)) + return node; + } + return null; + } + + /** + * Returns the start event occurrence attached to this graphNode. + * + * @return the start event occurrence attached to the graphNode + */ + public int getStartOccurrence() { + return startEventOccurrence; + } + + /** + * Returns the end event occurrence attached to this graphNode + * + * @return the start event occurrence attached to the graphNode + */ + public int getEndOccurrence() { + return endEventOccurrence; + } + + /** + * Computes the index of the first visible GraphNode for each ordered graph node lists depending on the visible area + * given in parameter + * + * @param x visible area top left corner x coordinate + * @param y visible area top left corner y coordinate + * @param width visible area width + * @param height visible area height + */ + public void updateIndex(int x, int y, int width, int height) { + if (!hasChilden) + return; + if(TmfUiTracer.isIndexTraced()) { + TmfUiTracer.traceIndex("*****************************\n"); //$NON-NLS-1$ + TmfUiTracer.traceIndex("Visible area position in virtual screen (x,y)= " + x + " " + y + "\n\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + Iterator it = nodes.keySet().iterator(); + while (it.hasNext()) { + String nodeType = it.next(); + int direction = 1; + int drawIndex = ((Integer) indexes.get(nodeType)).intValue(); + /* + * if (x==0) { drawIndex = 0; indexes.put(nodeType,new Integer(drawIndex)); } + */ + if ((nodes.get(nodeType) != null) && (((List) nodes.get(nodeType)).size() > 1)) { + if (((GraphNode) ((List) nodes.get(nodeType)).get(drawIndex)).positiveDistanceToPoint(x, y)) + direction = -1; + + if (drawIndex == 0) + direction = 1; + + if ((direction == -1) && (bnodes.get(nodeType) != null)) { + GraphNode currentNode = (GraphNode) ((List) nodes.get(nodeType)).get(drawIndex); + drawIndex = Arrays.binarySearch(((List) bnodes.get(nodeType)).toArray(new GraphNode[0]), ((List) nodes.get(nodeType)).get(drawIndex), currentNode.getBackComparator()); + nodes.put(nodeType, (List) bnodes.get(nodeType)); + if (drawIndex < 0) { + drawIndex = 0; + direction = 1; + } else + nodes.put(nodeType, (List) bnodes.get(nodeType)); + } + GraphNode prev = null; + + for (int i = drawIndex; i < ((List) nodes.get(nodeType)).size() && i >= 0; i = i + direction) { + drawIndex = i; + indexes.put(nodeType, Integer.valueOf(i)); + + GraphNode currentNode = (GraphNode) ((List) nodes.get(nodeType)).get(i); + + if (prev == null) + prev = currentNode; + + Comparator comp = currentNode.getComparator(); + HashMap sort = fSort; + + if (direction == -1) { + if (currentNode.getBackComparator() != null) { + comp = currentNode.getBackComparator(); + sort = bSort; + } + } + + if (i < ((List) nodes.get(nodeType)).size() - 1) { + GraphNode next = (GraphNode) ((List) nodes.get(nodeType)).get(i + 1); + + if ((comp != null) && (comp.compare(currentNode, next) == 1)) + sort.put(nodeType, Boolean.valueOf(true)); + } + if (direction == 1) { + if (((GraphNode) ((List) nodes.get(nodeType)).get(i)).positiveDistanceToPoint(x, y)) + break; + } else { + if (currentNode.getBackComparator() == null) { + if // (currentNode.isVisible(x,y,width,height) + (!currentNode.positiveDistanceToPoint(x, y)) + break; + } else { + if (currentNode.isVisible(x, y, width, height) && !currentNode.positiveDistanceToPoint(x, y)) { + if ((comp != null) && (comp.compare(currentNode, prev) <= 0)) + break; + } else if ((comp != null) && (comp.compare(currentNode, prev) <= 0)) + prev = currentNode; + } + } + } + + nodes.put(nodeType, fnodes.get(nodeType)); + if ((bnodes.get(nodeType) != null) && (direction == -1)) { + // nodes.put(nodeType,fnodes.get(nodeType)); + int index = ((Integer) indexes.get(nodeType)).intValue(); + List list = (List) nodes.get(nodeType); + List backList = (List) bnodes.get(nodeType); + GraphNode currentNode = (GraphNode) (backList.get(index)); + if (index > 0) { + index = Arrays.binarySearch(list.toArray(new GraphNode[0]), backList.get(index), currentNode.getComparator()); + if (index < 0) + index = 0; + indexes.put(nodeType, Integer.valueOf(index)); + } + } + + for (int i = drawIndex; i < ((List) nodes.get(nodeType)).size() && i >= 0; i++) { + GraphNode toDraw = (GraphNode) ((List) nodes.get(nodeType)).get(i); + toDraw.updateIndex(x, y, width, height); + if (!toDraw.isVisible(x, y, width, height)) + break; + } + } + if (TmfUiTracer.isIndexTraced()) { + TmfUiTracer.traceIndex("First drawn " + nodeType + " index = " + drawIndex + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + TmfUiTracer.traceIndex(nodeType + " found in " + 0 + " iterations\n"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + if (TmfUiTracer.isIndexTraced()) { + TmfUiTracer.traceIndex("*****************************\n"); //$NON-NLS-1$ + } + } + + /** + * Draws the children nodes on the given context.
+ * This method start width GraphNodes ordering if needed.
+ * After, depending on the visible area, only visible GraphNodes are drawn.
+ * + * @param context the context to draw to + * @param drawFrame indicate if the frame rectangle need to be redrawn + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode#draw(IGC) + */ + protected void drawChildenNodes(IGC context) { + + if (!hasChilden) + return; + // If the nodes have not been added ordered, the array is ordered + Iterator it = fSort.keySet().iterator(); + while (it.hasNext()) { + String nodeType = it.next(); + boolean sort = ((Boolean) fSort.get(nodeType)).booleanValue(); + if (sort) { + GraphNode[] temp = ((List) fnodes.get(nodeType)).toArray(new GraphNode[0]); + GraphNode node = (GraphNode) ((List) nodes.get(nodeType)).get(0); + Arrays.sort(temp, node.getComparator()); + fSort.put(nodeType, Boolean.valueOf(false)); + nodes.put(nodeType, Arrays.asList(temp)); + fnodes.put(nodeType, Arrays.asList(temp)); + if (TmfUiTracer.isSortingTraced()) + TmfUiTracer.traceSorting(nodeType + " array sorted\n"); //$NON-NLS-1$ + } + } + + Iterator it2 = bSort.keySet().iterator(); + while (it2.hasNext()) { + String nodeType = it2.next(); + boolean sort = ((Boolean) bSort.get(nodeType)).booleanValue(); + if (sort) { + GraphNode[] temp = ((List) bnodes.get(nodeType)).toArray(new GraphNode[0]); + GraphNode node = (GraphNode) ((List) nodes.get(nodeType)).get(0); + Arrays.sort(temp, node.getBackComparator()); + bSort.put(nodeType, Boolean.valueOf(false)); + bnodes.put(nodeType, Arrays.asList(temp)); + if (TmfUiTracer.isSortingTraced()) + TmfUiTracer.traceSorting(nodeType + " back array sorted\n"); //$NON-NLS-1$ + } + } + + if (TmfUiTracer.isDisplayTraced()) { + TmfUiTracer.traceDisplay("*****************************\n"); //$NON-NLS-1$ + } + + int arrayStep = 1; + if ((Metrics.getMessageFontHeigth() + Metrics.MESSAGES_NAME_SPACING * 2) * context.getZoom() < Metrics.MESSAGE_SIGNIFICANT_VSPACING) + arrayStep = Math.round(Metrics.MESSAGE_SIGNIFICANT_VSPACING / ((Metrics.getMessageFontHeigth() + Metrics.MESSAGES_NAME_SPACING * 2) * context.getZoom())); + + int count = 0; + Iterator it3 = fSort.keySet().iterator(); + while (it3.hasNext()) { + count = 0; + Object nodeType = it3.next(); + GraphNode node = (GraphNode) ((List) nodes.get(nodeType)).get(0); + context.setFont(Frame.getUserPref().getFont(node.prefId)); + int index = ((Integer) indexes.get(nodeType)).intValue(); + count = drawNodes(context, (List) nodes.get(nodeType), index, arrayStep); + if (TmfUiTracer.isDisplayTraced()) + TmfUiTracer.traceDisplay(count + " " + nodeType + " drawn, starting from index " + index + "\r\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + if (TmfUiTracer.isDisplayTraced()) { + TmfUiTracer.traceDisplay("*****************************\n"); //$NON-NLS-1$ + } + + } + + /** + * Draw the GraphNode stored in the given list, starting at index startIndex with the given step + * + * @param context the context to draw to + * @param list the GraphNodes list + * @param startIndex the start index + * @param step the step to browse the list + * @return the number of GraphNodes drawn + */ + protected int drawNodes(IGC context, List list, int startIndex, int step) { + if (!hasChilden) + return 0; + + GraphNode last = null; + int nodesCount = 0; + if (list.size() < 0) + return 0; + + GraphNode node = (GraphNode) list.get(0); + context.setFont(Frame.getUserPref().getFont(node.prefId)); + Comparator comparator = node.getComparator(); + for (int i = startIndex; i < list.size(); i = i + step) { + GraphNode toDraw = (GraphNode) list.get(i); + if (i < list.size() - 1) { + GraphNode next = (GraphNode) list.get(i + 1); + if ((comparator != null) && (comparator.compare(toDraw, next) == 1)) { + fSort.put(next.getArrayId(), Boolean.valueOf(true)); + } + } + int cx = context.getContentsX(); + int cy = context.getContentsY(); + int cw = context.getVisibleWidth(); + int ch = context.getVisibleHeight(); + // The arrays should be ordered, no needs to continue for this one + if (!toDraw.isVisible(cx, cy, cw, ch) && toDraw.positiveDistanceToPoint(cx + cw, cy + ch)) + break; + // ***Common*** nodes visibility + if ((!toDraw.isSameAs(last) || toDraw.isSelected()) && (toDraw.isVisible(context.getContentsX(), context.getContentsY(), context.getVisibleWidth(), context.getVisibleHeight()))) { + nodesCount++; + + toDraw.draw(context); + if (hasFocus()) + toDraw.drawFocus(context); + } + last = toDraw; + } + return nodesCount; + } + + public void drawFocus(IGC context) { + context.drawFocus(getX(), getY(), getWidth(), getHeight()); + } + + /** + * Determine if the given point (px,py) is contained in the rectangle (x,y,width,height) + * + * @param x the rectangle x coordinate + * @param y the rectangle y coordinate + * @param width the rectangle width + * @param height the rectangle height + * @param px the x coordinate of the point to test + * @param py the y coordinate of the point to test + * @return true if contained false otherwise + */ + public static boolean contains(int x, int y, int width, int height, int px, int py) { + int locX = x; + int locY = y; + int locWidth = width; + int locHeight = height; + + if (width < 0) { + locX = locX + width; + locWidth = -locWidth; + } + + if (height < 0) { + locY = locY + height; + locHeight = -locHeight; + } + return (px >= locX) && (py >= locY) && ((px - locX) <= locWidth) && ((py - locY) <= locHeight); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/HotSpot.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/HotSpot.java new file mode 100755 index 0000000000..f9a81bf886 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/HotSpot.java @@ -0,0 +1,138 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: HotSpot.java,v 1.2 2006/09/20 20:56:27 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IImage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; + +/** + * @author sveyrier + */ +public class HotSpot extends GraphNode { + + protected BasicExecutionOccurrence execOcc = null; + protected int occurrence = 0; + protected IImage image = null; + + /** + * The grahNode ID constant + */ + public static final String GLYPH = "Glyph"; //$NON-NLS-1$ + + public HotSpot() { + prefId = ISDPreferences.PREF_EXEC; + } + + public void setImage(IImage img) { + image = img; + } + + @Override + public int getX() { + if (execOcc != null) + return execOcc.getX() - 3; + else + return 0; + + } + + @Override + public int getY() { + if (execOcc != null) + return execOcc.getY(); + else + return 0; + } + + @Override + public int getWidth() { + if (execOcc != null) + return execOcc.getWidth() + 7; + else + return 0; + } + + @Override + public int getHeight() { + if (execOcc != null) + return execOcc.getWidth() + 10; + else + return 0; + } + + /** + * Set the lifeline on which the execution occurrence appears. + * + * @param theLifeline - the parent lifeline + */ + public void setExecution(BasicExecutionOccurrence occ) { + execOcc = occ; + execOcc.addNode(this); + } + + /** + * Get the lifeline on which the execution occurrence appears. + * + * @return - the parent lifeline + */ + public BasicExecutionOccurrence getExecOcc() { + return execOcc; + } + + public int getOccurrence() { + return occurrence; + } + + public void setOccurrence(int occ) { + occurrence = occ; + } + + @Override + public void draw(IGC context) { + + // The execution occurrence is selected + // if the owning lifeline is selected + if (isSelected() || (execOcc != null && execOcc.isSelected()) || (execOcc != null && execOcc.getLifeline() != null && execOcc.getLifeline().isSelected())) { + context.setBackground(Frame.getUserPref().getBackGroundColorSelection()); + context.setForeground(Frame.getUserPref().getForeGroundColorSelection()); + } else { + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_EXEC)); + context.setForeground(Frame.getUserPref().getForeGroundColor(ISDPreferences.PREF_EXEC)); + } + context.drawImage(image, getX(), getY(), getWidth(), getHeight()); + } + + @Override + public String getArrayId() { + return GLYPH; + } + + @Override + public boolean isVisible(int x, int y, int width, int height) { + return true; + } + + @Override + public boolean contains(int _x, int _y) { + int x = getX(); + int y = getY(); + int width = getWidth(); + int height = getHeight(); + + if (Frame.contains(x, y, width, height, _x, _y)) { + return true; + } + return false; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/ITimeRange.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/ITimeRange.java new file mode 100755 index 0000000000..f9b37e7f97 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/ITimeRange.java @@ -0,0 +1,28 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ITimeRange.java,v 1.2 2006/09/20 20:56:25 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; + +/** + * @author sveyrier + * + */ +public interface ITimeRange { + + public TmfTimestamp getStartTime(); + + public TmfTimestamp getEndTime(); + + public boolean hasTimeInfo(); +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Lifeline.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Lifeline.java new file mode 100755 index 0000000000..5d25adaaf7 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Lifeline.java @@ -0,0 +1,468 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: Lifeline.java,v 1.3 2008/01/24 02:28:49 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IColor; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IImage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; + +/** + * Lifeline is the UML2 lifeline graphical representation.
+ * Each lifeline owns a set of event occurrences. An event occurrence is the base element in UML2 to set an event in a + * sequence diagram.
+ * Event occurrence define the drawing order of graph node along a lifeline. In this lifeline implementation, event + * occurrences are just integer index. The event occurrences with the same value on different lifelines will correspond + * the same y coordinate value. + * + * @author sveyrier + * + */ +public class Lifeline extends GraphNode { + + /** + * The lifeline position in the containing frame + */ + protected int indexInFrame = 0; + + /** + * The frame where the lifeline is drawn + */ + protected Frame frame = null; + + /** + * The current event occurrence created in the lifeline + */ + protected int eventOccurrence = 0; + + public static final String LIFELINE_TAG = "Lifeline"; //$NON-NLS-1$ + + protected int category = -1; + + protected boolean hasTime = false; + + @Override + public int getX() { + return Metrics.FRAME_H_MARGIN + Metrics.LIFELINE_H_MAGIN + (indexInFrame - 1) * Metrics.swimmingLaneWidth(); + } + + @Override + public int getY() { + return 2 * Metrics.FRAME_NAME_H_MARGIN + Metrics.LIFELINE_VT_MAGIN / 2 + Metrics.getFrameFontHeigth() + Metrics.getLifelineHeaderFontHeigth() + Metrics.FRAME_V_MARGIN + 2 * Metrics.LIFELINE_HEARDER_TEXT_V_MARGIN; + } + + @Override + public int getWidth() { + return Metrics.getLifelineWidth(); + } + + @Override + public int getHeight() { + // Set room for two text lines + return Metrics.getLifelineFontHeigth()/** 2 */ + + 2 * Metrics.LIFELINE_NAME_H_MARGIN; + } + + public Lifeline() { + prefId = ISDPreferences.PREF_LIFELINE; + } + + /** + * Set the lifeline category for this lifeline. + * + * @param arrayIndex the index of the category to use + * @see Frame#setLifelineCategories(LifelineCategories[]) + */ + public void setCategory(int arrayIndex) { + category = arrayIndex; + } + + /** + * Returns the tooltip text for the lifeline. It is the combination between the category name(if any) and the + * lifeline name + * + * @return the tooltip text + */ + public String getToolTipText() { + if (category >= 0) { + LifelineCategories[] categories = frame.getLifelineCategories(); + if (category < categories.length) { + return categories[category].getName() + " " + getName(); //$NON-NLS-1$ + } else + return ""; //$NON-NLS-1$ + } else + return ""; //$NON-NLS-1$ + } + + /** + * Returns the index of the first visible Execution Occurrence in the execution occurrence array.
+ * Execution Occurrences are Y ordered in this array + * + * @return the first visible Execution Occurrence + */ + public int getExecOccurrenceDrawIndex() { + if (!hasChilden) + return 0; + if (indexes.get(BasicExecutionOccurrence.EXEC_OCC_TAG) != null) + return ((Integer) indexes.get(BasicExecutionOccurrence.EXEC_OCC_TAG)).intValue(); + else + return 0; + } + + /** + * Set the frame on which this lifeline must be drawn + * + * @param parentFrame + */ + protected void setFrame(Frame parentFrame) { + frame = parentFrame; + if (hasTime) { + frame.setHasTimeInfo(true); + } + if (frame.getMaxEventOccurrence() < getEventOccurrence() + 1) + frame.setMaxEventOccurrence(getEventOccurrence() + 1); + } + + /** + * Returns the frame which this lifeline is drawn + * + * @return the Frame + */ + protected Frame getFrame() { + return frame; + } + + /** + * Set the lifeline position index in the containing frame + * + * @param index the lifeline X position + */ + + protected void setIndex(int index) { + indexInFrame = index; + } + + /** + * Returns the lifeline position in de the containing frame + * + * @return the X position + */ + public int getIndex() { + return indexInFrame; + } + + /** + * Set the lifeline event occurrence to the value given in parameter This only change the current event occurrence, + * greater event created on this lifeline are still valid and usable. This also need to inform the frame of the + * operation mostly to store in the frame the greater event found in the diagram (used to determine the frame + * height) + * + * @param index the new current event occurrence + */ + public void setCurrentEventOccurrence(int eventOcc) { + if ((frame != null) && (frame.getMaxEventOccurrence() < eventOcc)) + frame.setMaxEventOccurrence(eventOcc); + eventOccurrence = eventOcc; + } + + /** + * Returns the last created event occurrence along the lifeline. + * + * @return the current event occurrence + */ + public int getEventOccurrence() { + return eventOccurrence; + } + + /** + * Creates a new event occurrence along the lifeline. + * + * @return the new created event occurrence + */ + public int getNewEventOccurrence() { + setCurrentEventOccurrence(eventOccurrence + 1); + return eventOccurrence; + } + + /** + * Adds the execution occurrence given in parameter to the lifeline.
+ * A Execution occurrence is never drawn in the frame instead it is added to a lifeline + * + * @param exec the execution occurrence to add + */ + public void addExecution(BasicExecutionOccurrence exec) { + exec.setLifeline(this); + addNode(exec); + if ((frame != null) && (frame.getMaxEventOccurrence() < exec.endEventOccurrence)) + frame.setMaxEventOccurrence(exec.endEventOccurrence); + } + + protected void setTimeInfo(boolean value) { + hasTime = value; + if ((frame != null) && (value == true)) + frame.setHasTimeInfo(value); + } + + /** + * @return true if at least one execution occurrence has time info + */ + public boolean hasTimeInfo() { + return hasTime; + } + + /** + * Returns the list of execution occurrence on this lifeline + * + * @return the execution occurrence list + */ + public List getExecutions() { + if (hasChilden) + return (List) nodes.get(BasicExecutionOccurrence.EXEC_OCC_TAG); + else + return new ArrayList(); + } + + @Override + public boolean contains(int _x, int _y) { + int x = getX(); + int y = getY(); + int width = getWidth(); + int height = getHeight(); + + if (frame == null) + return false; + if (Frame.contains(x, y, width, height, _x, _y)) { + return true; + } + if (Frame.contains(x + Metrics.getLifelineWidth() / 2 - Metrics.EXECUTION_OCCURRENCE_WIDTH / 2, y + height, Metrics.EXECUTION_OCCURRENCE_WIDTH, (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * frame.getMaxEventOccurrence() + + Metrics.LIFELINE_VB_MAGIN - 4, _x, _y)) { + return true; + } + + height = Metrics.getLifelineFontHeigth() + 2 * Metrics.LIFELINE_HEARDER_TEXT_V_MARGIN; + int hMargin = (Metrics.LIFELINE_VT_MAGIN - height) / 2; + + if (hMargin >= 2) { + if (frame.getVisibleAreaY() < y - height - hMargin) { + if (Frame.contains(x - Metrics.LIFELINE_SPACING / 2 + 1, y - height - hMargin, Metrics.swimmingLaneWidth() - 2, height + 1, _x, _y)) + return true; + } else { + if (Frame.contains(x - Metrics.LIFELINE_SPACING / 2 + 1, frame.getVisibleAreaY(), Metrics.swimmingLaneWidth() - 2, height, _x, _y)) + return true; + } + } + if (getNodeAt(_x, _y) != null) + return true; + return false; + } + + /** + * Returns the lifeline visibility for the given visible area + * + * @param vx + * @param vy + * @param vwidth + * @param vheight + * @return true if visible false otherwise + */ + @Override + public boolean isVisible(int vx, int vy, int vwidth, int vheight) { + int x = getX(); + int width = getWidth(); + if (((x >= vx) && (x <= vx + vwidth)) || ((x + width >= vx) && (x <= vx))) + return true; + return false; + } + + protected void drawName(IGC context) { + int x = getX(); + int y = getY(); + int height = Metrics.getLifelineHeaderFontHeigth() + 2 * Metrics.LIFELINE_HEARDER_TEXT_V_MARGIN; + int hMargin = Metrics.LIFELINE_VT_MAGIN / 4;// (Metrics.LIFELINE_NAME_H_MARGIN)/2; + + context.setLineStyle(context.getLineSolidStyle()); + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_LIFELINE_HEADER)); + context.setForeground(Frame.getUserPref().getForeGroundColor(ISDPreferences.PREF_LIFELINE_HEADER)); + context.setFont(Frame.getUserPref().getFont(ISDPreferences.PREF_LIFELINE_HEADER)); + if (hMargin >= 0) { + if (frame.getVisibleAreaY() < y - height - hMargin) { + context.fillRectangle(x - Metrics.LIFELINE_SPACING / 2 + 1, y - height - hMargin, Metrics.swimmingLaneWidth() - 2, height); + context.drawRectangle(x - Metrics.LIFELINE_SPACING / 2 + 1, y - height - hMargin, Metrics.swimmingLaneWidth() - 2, height); + context.setForeground(Frame.getUserPref().getFontColor(ISDPreferences.PREF_LIFELINE_HEADER)); + context.drawTextTruncatedCentred(getName(), x + Metrics.LIFELINE_NAME_V_MARGIN - Metrics.LIFELINE_SPACING / 2 + 1, y - height - hMargin, Metrics.swimmingLaneWidth() - 2 * Metrics.LIFELINE_NAME_V_MARGIN - 2, height, true); + } else { + context.fillRectangle(x - Metrics.LIFELINE_SPACING / 2 + 1, frame.getVisibleAreaY(), Metrics.swimmingLaneWidth() - 2, height); + context.drawRectangle(x - Metrics.LIFELINE_SPACING / 2 + 1, frame.getVisibleAreaY(), Metrics.swimmingLaneWidth() - 2, height); + context.setForeground(Frame.getUserPref().getFontColor(ISDPreferences.PREF_LIFELINE_HEADER)); + context.drawTextTruncatedCentred(getName(), x - Metrics.LIFELINE_SPACING / 2 + Metrics.LIFELINE_NAME_V_MARGIN + 1, frame.getVisibleAreaY(), Metrics.swimmingLaneWidth() - 2 * Metrics.LIFELINE_NAME_V_MARGIN - 2, height, true); + } + } + } + + /** + * Force the lifeline to be drawn at the given coordinate + * + * @param context - the context to draw into + * @param x1 - the x coordinate + * @param y1 - the y coordinate + */ + public void draw(IGC context, int x, int y) { + // Set the draw color depending if the lifeline must be selected or not + context.setLineWidth(Metrics.NORMAL_LINE_WIDTH); + if (isSelected()) { + if (Frame.getUserPref().useGradienColor()) { + context.setGradientColor(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_LIFELINE)); + } + context.setBackground(Frame.getUserPref().getBackGroundColorSelection()); + context.setForeground(Frame.getUserPref().getForeGroundColorSelection()); + } else { + if (Frame.getUserPref().useGradienColor()) { + context.setGradientColor(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_LIFELINE)); + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_FRAME)); + } else + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_LIFELINE)); + context.setForeground(Frame.getUserPref().getForeGroundColor(ISDPreferences.PREF_LIFELINE)); + } + // Store the lifeline coordinates to save some calls + int width = getWidth(); + int height = getHeight(); + + // Draw the rectangle which contain the lifeline name + if (Frame.getUserPref().useGradienColor()) { + context.fillGradientRectangle(x, y, width, height / 2 - 7, true); + context.fillRectangle(x, y + height / 2 - 8, width, +height / 2 - 5); + context.fillGradientRectangle(x, y + height, width, -height / 2 + 6, true); + } else + context.fillRectangle(x, y, width, height); + context.drawRectangle(x, y, width, height); + + if (category >= 0) { + LifelineCategories[] categories = frame.getLifelineCategories(); + if (category < categories.length) { + IImage image = categories[category].getImage(); + if (image != null) + context.drawImage(image, x, y, width, height); + } + } + + // Draw the lifeline label into the rectangle + // The label is truncated if it cannot fit + IColor temp = context.getForeground(); + context.setFont(Frame.getUserPref().getFont(ISDPreferences.PREF_LIFELINE)); + context.setForeground(Frame.getUserPref().getFontColor(ISDPreferences.PREF_LIFELINE)); + context.drawTextTruncatedCentred(getName(), x + Metrics.LIFELINE_NAME_V_MARGIN, y, Metrics.getLifelineWidth() - 2 * Metrics.LIFELINE_NAME_V_MARGIN, height, true); + + context.setLineStyle(context.getLineDashStyle()); + context.setForeground(temp); + int oldStyle = context.getLineStyle(); + + // Now draw the lifeline vertical line + // this line height depends on a stop assignment + // if there is no stop the line is drawn to the bottom of the frame + + // by default set the height to reach the frame bottom + int dashedLineEnd = y + height + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * frame.getMaxEventOccurrence() + Metrics.LIFELINE_VB_MAGIN; + /* + * if (stop != null) { dashedLineEnd = stop.getY(); } + */ + + if (isSelected()) { + context.setForeground(Frame.getUserPref().getBackGroundColorSelection()); + context.setLineWidth(5); + context.drawLine(x + Metrics.getLifelineWidth() / 2, y + height, x + Metrics.getLifelineWidth() / 2, dashedLineEnd - 4); + context.setForeground(Frame.getUserPref().getForeGroundColorSelection()); + } + + context.setLineWidth(Metrics.NORMAL_LINE_WIDTH); + context.drawLine(x + Metrics.getLifelineWidth() / 2, y + height, x + Metrics.getLifelineWidth() / 2, dashedLineEnd - 4); + context.drawLine(x + Metrics.getLifelineWidth() / 2, y + height, x + Metrics.getLifelineWidth() / 2, dashedLineEnd - 4); + context.setLineStyle(oldStyle); + + context.setLineStyle(context.getLineSolidStyle()); + + if (hasFocus()) + drawFocus(context); + + super.drawChildenNodes(context); + } + + /** + * Draws the select execution occurrence region using the given color + * + * @param context the graphical context + * @param startEvent the region start + * @param nbEvent the region height + * @param color the color to use + */ + public void highlightExecOccurrenceRegion(IGC context, int startEvent, int nbEvent, IColor color) { + IColor backupColor = context.getBackground(); + context.setBackground(color); + int x = getX() + Metrics.getLifelineWidth() / 2 - Metrics.EXECUTION_OCCURRENCE_WIDTH / 2; + int y = getY() + getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * startEvent; + int width = Metrics.EXECUTION_OCCURRENCE_WIDTH; + int height = ((Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing())) * nbEvent; + context.fillRectangle(x, y, width, height); + context.setBackground(backupColor); + } + + @Override + public void draw(IGC context) { + draw(context, getX(), getY()); + } + + @Override + public String getArrayId() { + return LIFELINE_TAG; + } + + @Override + public boolean positiveDistanceToPoint(int x, int y) { + if (getX() > x - Metrics.swimmingLaneWidth()) + return true; + return false; + } + + @Override + public GraphNode getNodeAt(int x, int y) { + int vy = 0; + int vh = 0; + if (getFrame() != null) { + vy = getFrame().getVisibleAreaY(); + vh = getFrame().getVisibleAreaHeight(); + } else + return null; + if (getExecutions() == null) + return null; + for (int i = getExecOccurrenceDrawIndex(); i < getExecutions().size(); i++) { + GraphNode node = (GraphNode) getExecutions().get(i); + if (node.getHeight() < 0) { + if (node.getY() + node.getHeight() > vy + vh) + break; + } else { + if (node.getY() > vy + vh) + break; + } + if (node.contains(x, y)) { + GraphNode internal = node.getNodeAt(x, y); + if (internal != null) + return internal; + else + return node; + } + } + return null; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/LifelineCategories.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/LifelineCategories.java new file mode 100755 index 0000000000..0de1ae4e35 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/LifelineCategories.java @@ -0,0 +1,67 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: LifelineCategories.java,v 1.2 2006/09/20 20:56:25 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IImage; + +/** + * LifelineCategories is used to assign additional description for + * lifelines of the same type. This consists in providing a type name and an icon. + * The icon will be displayed in the rectangle which contains the lifeline name. + * The category name is only display in the lifeline tooltip. + */ +public class LifelineCategories { + + /** + * The category name + */ + protected String name = null; + /** + * The category image + */ + protected IImage categoryImage = null; + + /** + * Returns the category name + * @return the category name + */ + public String getName() { + return name; + } + + /** + * Set the category name + * @param string the name + */ + public void setName(String string) { + name = string; + } + + /** + * Returns the category icon + * @return the category icon + */ + public IImage getImage() + { + return categoryImage; + } + + /** + * Set the category icon + * @param image the icon + */ + public void setImage(IImage image) + { + categoryImage = image; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Metrics.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Metrics.java new file mode 100755 index 0000000000..2beb3cd704 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Metrics.java @@ -0,0 +1,275 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: Metrics.java,v 1.3 2008/01/24 02:28:49 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +/** + * This class contains the metrics used to layout a sequence diagram on a view The class method are mostly used in + * combination with the preferences + * + * @author sveyrier + * + */ +public class Metrics { + + /** + * Space between the Frame and the top of the View This also represent the space between the frame and the bottom of + * the View + */ + public static final int FRAME_H_MARGIN = 10; + /** + * Space between the Frame and the left of the View This also represent the space between the Frame and the right of + * the View + */ + public static final int FRAME_V_MARGIN = 10; + /** + * Space between the Lifelines and the right of the Frame + */ + public static final int LIFELINE_H_MAGIN = 23; + + /** + * Space between the Lifelines and the bottom of the Frame + */ + public static final int LIFELINE_VB_MAGIN = 20; + /** + * Space between the Lifelines and the top of the Frame + */ + public static final int LIFELINE_VT_MAGIN = 30;// 18 + /** + * Vertical space between the lifeline name and the rectangle which contains that name This is only for the + * "always visible" lifeline name rectangle + */ + public static final int LIFELINE_HEARDER_TEXT_V_MARGIN = 4; + /** + * Vertical spacing between messages + */ + protected static final int MESSAGES_SPACING = 30; + /** + * Vertical spacing between the message and its name + */ + public static final int MESSAGES_NAME_SPACING = 10; + /** + * Horizontal spacing between the Frame name and its containing rectangle + */ + public static final int FRAME_NAME_H_MARGIN = 4; + /** + * Vertical spacing between the Frame name and its containing rectangle + */ + public static final int FRAME_NAME_V_MARGIN = 8; + /** + * Horizontal spacing between the lifeline name and its containing rectangle + */ + public static final int LIFELINE_NAME_H_MARGIN = 14; + /** + * Vertical spacing between the lifeline name and its containing rectangle + */ + public static final int LIFELINE_NAME_V_MARGIN = 20; + /** + * Space between the rectangles which contain the Lifelines name + */ + public static final int LIFELINE_SPACING = 45; + /** + * The circle ray used to draw the circle which compose Found and Lost messages + */ + public static final int MESSAGE_CIRCLE_RAY = 5; + /** + * Execution occurrence vertical width + */ + public static final int EXECUTION_OCCURRENCE_WIDTH = 8; + /** + * The square width which contains the Stop representation (a cross) + */ + public static final int STOP_WIDTH = 20; + + public static final int INTERNAL_MESSAGE_WIDTH = 20; + public static final int SYNC_INTERNAL_MESSAGE_HEIGHT = 10; + + /** + * Line width used when drawing selected GraphNode + */ + public static final int SELECTION_LINE_WIDTH = 5; + /** + * Line width used when drawing non selected GraphNode + */ + public static final int NORMAL_LINE_WIDTH = 1; + public static final int INTERNAL_MESSAGE_V_MARGIN = 10; + + /** + * Used to sample the diagram. When the lifeline spacing is smaller than this constant when zooming out then less + * lifelines are displayed to avoid lifelines overlapping and mainly saving some execution time + */ + public static final int LIFELINE_SIGNIFICANT_HSPACING = 10; + /** + * Used to sample the diagram. When the message spacing is smaller than this constant when zooming out then less + * message are displayed to avoid message overlapping and mainly saving some execution time + */ + public static final int MESSAGE_SIGNIFICANT_VSPACING = 1; + + // Used for internal syncMessages only + public static final int MESSAGE_SELECTION_TOLERANCE = 30; + + public static final int FOCUS_DRAWING_MARGIN = 10; + + static private int lifelineFontHeight = 0; + static private int messageFontHeight = 0; + static private int frameFontHeight = 0; + static private int lifelineHeaderFontHeight = 0; + static private int lifelineFontWidth = 0; + static private int lifeLineWidth = 119; + + static private int forcedEventSpacing = -1; + + /** + * Set the character height used to draw the lifeline name + * + * @param height the character height + */ + static public void setLifelineFontHeight(int height) { + lifelineFontHeight = height; + } + + /** + * Set the character width used to draw the lifeline name + * + * @param width the character width + */ + static public void setLifelineFontWidth(int width) { + lifelineFontWidth = width; + } + + /** + * Set the character height used to draw the message name + * + * @param fontHeight the character height + */ + static public void setMessageFontHeight(int fontHeight) { + messageFontHeight = fontHeight; + } + + /** + * Returns the character height used to draw the lifeline name + * + * @return the character height + */ + static public int getFrameFontHeigth() { + return frameFontHeight; + } + + /** + * Set the character height used to draw the message name + * + * @param fontHeight the character height + */ + static public void setFrameFontHeight(int fontHeight) { + frameFontHeight = fontHeight; + } + + /** + * Returns the character height used to draw the lifeline name + * + * @return the character height + */ + static public int getLifelineHeaderFontHeigth() { + return lifelineHeaderFontHeight; + } + + /** + * Set the character height used to draw the message name + * + * @param fontHeight the character height + */ + static public void setLifelineHeaderFontHeight(int fontHeight) { + lifelineHeaderFontHeight = fontHeight; + } + + /** + * Returns the character height used to draw the lifeline name + * + * @return the character height + */ + static public int getLifelineFontHeigth() { + return lifelineFontHeight; + } + + /** + * Returns the character height used to draw the message name + * + * @return the character height + */ + static public int getMessageFontHeigth() { + if (forcedEventSpacing >= 0) + return 0; + else + return messageFontHeight; + } + + /** + * This is the vertical space used by a Lifeline (mostly the rectangle which contain its name) + * + * @return the vertical space used by a Lifeline + */ + static public int getLifelineWidth() { + return lifeLineWidth; + } + + /** + * Set the vertical space used by a Lifeline (mostly the rectangle which contain its name) + * + * @param value the vertical space + */ + static public void setLifelineWidth(int value) { + lifeLineWidth = value; + } + + /** + * Returns the swimming lane width + * + * @return the swimming lane width + */ + static public int swimmingLaneWidth() { + return getLifelineWidth() + LIFELINE_SPACING; + } + + /** + * Returns the character width used to draw the Lifelines name + * + * @return the average character width + */ + static public int getAverageCharWidth() { + return lifelineFontWidth; + } + + static public int getMessagesSpacing() { + if (forcedEventSpacing >= 0) + return forcedEventSpacing; + else + return MESSAGES_SPACING; + } + + /** + * Sets the forced event spacing value . + * + * @param eventSpacing + */ + static public void setForcedEventSpacing(int eventSpacing) { + forcedEventSpacing = eventSpacing; + } + + /** + * Gets the forced event spacing value. + * + * @return forcedEventSpacing + */ + static public int getForcedEventSpacing() { + return forcedEventSpacing; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/SDTimeEvent.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/SDTimeEvent.java new file mode 100755 index 0000000000..17f541f8ab --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/SDTimeEvent.java @@ -0,0 +1,45 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: TimeEvent.java,v 1.2 2006/09/20 20:56:25 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; + +/** + * @author sveyrier + * + */ +public class SDTimeEvent { + + protected TmfTimestamp time; + protected int event; + protected ITimeRange node; + + public SDTimeEvent(TmfTimestamp _time, int _event, ITimeRange _node) { + time = _time; + event = _event; + node = _node; + } + + public TmfTimestamp getTime() { + return time; + } + + public int getEvent() { + return event; + } + + public ITimeRange getGraphNode() { + return node; + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Stop.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Stop.java new file mode 100755 index 0000000000..fd21f19f6d --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/Stop.java @@ -0,0 +1,139 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: Stop.java,v 1.2 2006/09/20 20:56:25 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; + +/** + * It is the UML2 stop graphical representation in the sequence diagram viewer.
+ * This draw a cross on the lifeline. The stop y coordinate depend on the event occurrence when it appears.
+ * A stop is never drawn it is assigned to a lifeline.
+ *
+ * + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline#setStop(Stop) + * @author sveyrier + * + */ +public class Stop extends GraphNode { + + /** + * The owning lifeline on which the stop appears + */ + protected Lifeline lifeline = null; + + /** + * The graphNode ID + */ + public static final String STOP = "STOP"; //$NON-NLS-1$ + + /** + * This basically represents the time when the stop occurs on the owning Lifeline + * + * @see Lifeline Lifeline for more event occurence details + */ + protected int eventOccurrence = 0; + + @Override + public int getX() { + if (lifeline == null) + return 0; + return lifeline.getX() + Metrics.getLifelineWidth() / 2 - Metrics.STOP_WIDTH / 2; + } + + @Override + public int getY() { + if (lifeline == null) + return 0; + return lifeline.getY() + lifeline.getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * eventOccurrence - Metrics.STOP_WIDTH / 2; + } + + @Override + public int getWidth() { + if (lifeline == null) + return 0; + return Metrics.STOP_WIDTH; + } + + @Override + public int getHeight() { + if (lifeline == null) + return 0; + return Metrics.STOP_WIDTH; + } + + /** + * Set the lifeline on which the stop must be draw + * + * @param theLifeline The the stop owing lifeline + */ + public void setLifeline(Lifeline theLifeline) { + lifeline = theLifeline; + } + + /** + * Set the event occurrence when this stop appears + * + * @param occurrence the eventOccurence to assign to the stop + */ + public void setEventOccurrence(int occurrence) { + eventOccurrence = occurrence; + } + + @Override + public void draw(IGC context) { + // Set the appropriate color depending if the graph node if selected or not + if (lifeline.isSelected()) { + context.setForeground(Frame.getUserPref().getBackGroundColorSelection()); + context.setLineWidth(Metrics.SELECTION_LINE_WIDTH); + int lastWidth = context.getLineWidth(); + context.setLineWidth(9); + // Draw a cross on the lifeline + context.drawLine(getX(), getY(), getX() + getWidth(), getY() + getHeight()); + context.drawLine(getX() + getWidth(), getY(), getX(), getY() + getHeight()); + // restore the context + context.setLineWidth(lastWidth); + context.setBackground(Frame.getUserPref().getBackGroundColorSelection()); + context.setForeground(Frame.getUserPref().getForeGroundColorSelection()); + } else { + context.setBackground(Frame.getUserPref().getBackGroundColor(ISDPreferences.PREF_LIFELINE)); + context.setForeground(Frame.getUserPref().getForeGroundColor(ISDPreferences.PREF_LIFELINE)); + } + int lastWidth = context.getLineWidth(); + context.setLineWidth(3); + // Draw a cross on the lifeline + context.drawLine(getX(), getY(), getX() + getWidth(), getY() + getHeight()); + context.drawLine(getX() + getWidth(), getY(), getX(), getY() + getHeight()); + // restore the context + context.setLineWidth(lastWidth); + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode#getArrayId() + */ + @Override + public String getArrayId() { + return STOP; + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode#contains(int, int) + */ + @Override + public boolean contains(int x, int y) { + // TODO Auto-generated method stub + return false; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/SyncMessage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/SyncMessage.java new file mode 100755 index 0000000000..c9c3fbdb01 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/SyncMessage.java @@ -0,0 +1,263 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: SyncMessage.java,v 1.2 2006/09/20 20:56:25 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import java.util.Comparator; + +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SortSyncMessageComparator; + +/** + * A SyncMessage is a synchronous message which appear at the same event occurrence on both lifeline ends (sender and + * receiver).
+ * A Sync message is usually drawn horizontally.
+ *
+ *
+ * Usage example: + * + *
+ * Frame frame;
+ * Lifeline lifeLine1;
+ * Lifeline lifeLine2;
+ * 
+ * SyncMessage message = new SyncMessage();
+ * // Create a new event occurrence on each lifeline
+ * lifeline1.getNewOccurrenceIndex();
+ * lifeline2.getNewOccurrenceIndex();
+ * // Set the message sender and receiver
+ * message.setStartLifeline(lifeLine1);
+ * message.setEndLifline(lifeline2);
+ * message.setName("Message label");
+ * // add the message to the frame
+ * frame.addMessage(message);
+ * 
+ * + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline Lifeline for more event occurence details + * @author sveyrier + * + */ +public class SyncMessage extends BaseMessage implements ITimeRange { + + /** + * The associated message return + */ + protected SyncMessageReturn messageReturn; + + /** + * The time when the message occurs + */ + protected TmfTimestamp eventTime = new TmfTimestamp(); + + public static final String SYNC_MESS_TAG = "SyncMessage"; //$NON-NLS-1$ + + protected boolean hasTime = false; + + public SyncMessage() { + prefId = ISDPreferences.PREF_SYNC_MESS; + } + + /** + * Ensure both lifelines have the same event occurrence (the greater found on each lifeline) + */ + protected void syncLifelinesEventOccurrence() { + if ((getStartLifeline() != null) && (getEndLifeline() != null)) { + int newIndex = 0; + if (getStartLifeline().getEventOccurrence() > getEndLifeline().getEventOccurrence()) + newIndex = getStartLifeline().getEventOccurrence(); + else + newIndex = getEndLifeline().getEventOccurrence(); + getStartLifeline().setCurrentEventOccurrence(newIndex); + getEndLifeline().setCurrentEventOccurrence(newIndex); + setEventOccurrence(getStartLifeline().getEventOccurrence()); + } + } + + /** + * Set the lifeLine from which the message has been sent.
+ * A new event occurrence will be created on this lifeLine.
+ * SyncMessage must occur at the same event occurrence on both lifeline, this method is responsible to synchronize the + * event occurrence on each lifeline (the greater value will be used).
+ * This synchronization is only done if the end lifeline has already been set. + * + * @param lifeline the message sender + */ + public void autoSetStartLifeline(Lifeline lifeline) { + lifeline.getNewEventOccurrence(); + setStartLifeline(lifeline); + } + + /** + * Set the lifeLine which has receiver the message.
+ * A new EventOccurence will be create on this lifeLine.
+ * SyncMessage must occur at the same event occurrence on both lifeline, this method is responsible to synchronize the + * event occurrence on each lifeline (the greater value will be used).
+ * This synchronization is only done if the start lifeline has already been set. + * + * @param lifeline the message receiver + */ + public void autoSetEndLifeline(Lifeline lifeline) { + lifeline.getNewEventOccurrence(); + setEndLifeline(lifeline); + } + + /** + * Set the lifeLine which has receiver the message.
+ * SyncMessage must occur at the same event occurrence on both lifeline, this method is responsible to synchronize the + * event occurrence on each lifeline (the greater value will be used).
+ * This synchronization is only done if the start lifeline has already been set. + * + * @param lifeline the message receiver + */ + @Override + public void setStartLifeline(Lifeline lifeline) { + super.setStartLifeline(lifeline); + if ((getEndLifeline() == null)) { + setEventOccurrence(getStartLifeline().getEventOccurrence()); + } else + syncLifelinesEventOccurrence(); + } + + /** + * Set the lifeLine which has receiver the message.
+ * SyncMessage must occur at the same event occurrence on both lifelines, this method is responsible to synchronize the + * event occurrence on each lifeline (the greater value will be used).
+ * This synchronization is only done if the start lifeline has already been set. + * + * @param lifeline the message receiver + */ + @Override + public void setEndLifeline(Lifeline lifeline) { + super.setEndLifeline(lifeline); + if ((getStartLifeline() == null)) { + setEventOccurrence(getEndLifeline().getEventOccurrence()); + } else + syncLifelinesEventOccurrence(); + } + + /** + * Set the event occurrence when this message occurs.
+ * + * @param occurrence the event occurrence to assign to this message.
+ * @see Lifeline Lifeline for more event occurence details + */ + @Override + protected void setEventOccurrence(int occurrence) { + startEventOccurrence = occurrence; + endEventOccurrence = occurrence; + } + + /** + * Set the message return associated with this message. + * + * @param message the message return to associate + */ + protected void setMessageReturn(SyncMessageReturn message) { + messageReturn = message; + } + + /** + * Returns the syncMessageReturn associated to this syncMessage + * + * @return the message return + */ + public SyncMessageReturn getMessageReturn() { + return messageReturn; + } + + /** + * Set the time when the message occurs + * + * @param time the time when the message occurs + */ + public void setTime(TmfTimestamp time) { + eventTime = time.clone(); + hasTime = true; + if (getStartLifeline() != null && getStartLifeline().getFrame() != null) + getStartLifeline().getFrame().setHasTimeInfo(true); + else if (getEndLifeline() != null && getEndLifeline().getFrame() != null) + getEndLifeline().getFrame().setHasTimeInfo(true); + } + + /** + * Returns the time when the message begin + * + * @return the time + */ + @Override + public TmfTimestamp getEndTime() { + return eventTime; + } + + /** + * Returns the time when the message end + * + * @return the time + */ + @Override + public TmfTimestamp getStartTime() { + return eventTime; + } + + @Override + public boolean hasTimeInfo() { + return hasTime; + } + + @Override + public void draw(IGC context) { + if (!isVisible()) + return; + // Draw it selected?*/ + if (!isSelected()) { + context.setBackground(Frame.getUserPref().getBackGroundColor(prefId)); + context.setForeground(Frame.getUserPref().getForeGroundColor(prefId)); + } + super.draw(context); + } + + @Override + public boolean isVisible(int x, int y, int width, int height) { + if (getY() > y + height + + // take into account the message name drawn above the arrow + Metrics.MESSAGES_NAME_SPACING + Metrics.getMessageFontHeigth()) + return false; + + // UML2 lost/found message visibility special case + // Others visibility cases are perform in the ***common*** case + if ((endLifeline == null && startLifeline != null) || (endLifeline != null && startLifeline == null)) { + if (x + width > getX() + getWidth() && x < getX() + getWidth()) + return true; + } + // ***Common*** syncMessages visibility + return super.isVisible(x, y, width, height); + } + + @Override + public Comparator getComparator() { + return new SortSyncMessageComparator(); + } + + @Override + public String getArrayId() { + return SYNC_MESS_TAG; + } + + @Override + public boolean positiveDistanceToPoint(int x, int y) { + if (getY() > y) + return true; + return false; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/SyncMessageReturn.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/SyncMessageReturn.java new file mode 100755 index 0000000000..d9e43712ce --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/core/SyncMessageReturn.java @@ -0,0 +1,87 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: SyncMessageReturn.java,v 1.2 2006/09/20 20:56:25 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.core; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; + +/** + * The message return graph node implementation.
+ * This class differs on the SynMessage class only on the drawing line style (dashed instead of plain line).
+ * Message return are generally associated to a message. This means, they are connected to the same lifelines than the + * associated message but in the opposite direction and for a different event occurrence.
+ *
+ * WARNING: The association validity is not checked, it is not necessary to provide a valid association, not even needed + * to set an association to drawn a message with a message return style.
+ * + * + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SyncMessage SyncMessage for usage example + * @author sveyrier + * + */ +public class SyncMessageReturn extends SyncMessage { + + /** + * The associated message(the message it is the return). + */ + protected SyncMessage message = null; + + public static final String SYNC_MESS_RET_TAG = "SyncMessageRet"; //$NON-NLS-1$ + + public SyncMessageReturn() { + prefId = ISDPreferences.PREF_SYNC_MESS_RET; + } + + /** + * Set the associated message (the message it is the return).
+ * Setting the association will activate the navigation in the default sequence diagram implementation to the + * message when the user right click on this message return.
+ * + * @param parentMessage the message to associate + */ + public void setMessage(SyncMessage parentMessage) { + message = parentMessage; + message.setMessageReturn(this); + } + + /** + * Returns the syncMessage associated to this SyncMessageReturn + * + * @return the associated message + */ + public SyncMessage getMessage() { + return message; + } + + @Override + public void draw(IGC context) { + if (!isVisible()) + return; + int oldStyle = context.getLineStyle(); + // Message return are dashed + context.setLineStyle(context.getLineDotStyle()); + // Draw it selected?*/ + if (!isSelected()) { + context.setBackground(Frame.getUserPref().getBackGroundColor(prefId)); + context.setForeground(Frame.getUserPref().getForeGroundColor(prefId)); + } + super.draw(context); + // restore the context + context.setLineStyle(oldStyle); + } + + @Override + public String getArrayId() { + return SYNC_MESS_RET_TAG; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IColor.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IColor.java new file mode 100755 index 0000000000..3798c8d5d9 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IColor.java @@ -0,0 +1,34 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: IColor.java,v 1.2 2006/09/20 20:56:27 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings; + +/** + * @author sveyrier + * + */ +public interface IColor { + + /** + * Returns the contained color. Returned object must be an instance of org.eclipse.swt.graphics.Color if used with + * the org.eclipse.linuxtools.tmf.ui.views.uml2sd.NGC graphical context + * + * @return the color + */ + public Object getColor(); + + /** + * Disposes the color + */ + public void dispose(); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IFont.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IFont.java new file mode 100755 index 0000000000..175f285312 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IFont.java @@ -0,0 +1,34 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: IFont.java,v 1.2 2006/09/20 20:56:27 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings; + +/** + * @author sveyrier + * + */ +public interface IFont { + + /** + * Returns the contained font. Returned object must be an instance of org.eclipse.swt.graphics.Font if used with the + * org.eclipse.linuxtools.tmf.ui.views.uml2sd.NGC graphical context + * + * @return the font + */ + public Object getFont(); + + /** + * Disposes the font + */ + public void dispose(); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IGC.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IGC.java new file mode 100755 index 0000000000..32079bba3e --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IGC.java @@ -0,0 +1,366 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: IGC.java,v 1.2 2006/09/20 20:56:27 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings; + +/** + * @author sveyrier + * + */ +public interface IGC { + + /** + * Set the current line style + * + * @param style the new line style + */ + public abstract void setLineStyle(int style); + + /** + * Returns current the line style used in the graphical context + * + * @return the current line style + */ + public abstract int getLineStyle(); + + /** + * Returns the contents x coordinate that is at the upper left corner of the view + * + * @return the contents x coordinate + */ + public abstract int getContentsX(); + + /** + * Returns the contents y coordinate that is at the upper left corner of the view + * + * @return the contents y coordinate + */ + public abstract int getContentsY(); + + /** + * Returns the contents visible width + * + * @return the contents width + */ + public abstract int getVisibleWidth(); + + /** + * Returns the contents visible height + * + * @return the contents height + */ + public abstract int getVisibleHeight(); + + /** + * Translates the given contents x coordinate into view x coordinate + * + * @param x the x coordinate to translate + * @return the corresponding view x coordinate + */ + public abstract int contentsToViewX(int x); + + /** + * Translates the given contents y coordinate into view y coordinate + * + * @param y the y coordinate to translate + * @return the corresponding view y coordinate + */ + public abstract int contentsToViewY(int y); + + /** + * Draws a line, using the foreground color, between the points (x1, y1) and (x2, y2). + * + * @param x1 the first point's x coordinate + * @param y1 the first point's y coordinate + * @param x2 the second point's x coordinate + * @param y2 the second point's y coordinate + */ + public abstract void drawLine(int x1, int y1, int x2, int y2); + + /** + * Draws the outline of the rectangle specified by the arguments, using the receiver's foreground color. The left + * and right edges of the rectangle are at x and x + width. The top and bottom edges are at y and y + height. + * + * @param x the x coordinate of the rectangle to be drawn + * @param y the y coordinate of the rectangle to be drawn + * @param width the width of the rectangle to be drawn + * @param height the height of the rectangle to be drawn + */ + public abstract void drawRectangle(int x, int y, int width, int height); + + /** + * Draws a rectangle, based on the specified arguments, which has the appearance of the platform's focus rectangle + * if the platform supports such a notion, and otherwise draws a simple rectangle in the receiver's foreground + * color. + * + * @param x the x coordinate of the rectangle + * @param y the y coordinate of the rectangle + * @param width the width of the rectangle + * @param height the height of the rectangle + */ + public abstract void drawFocus(int x, int y, int width, int height); + + /** + * Fills the interior of the closed polygon which is defined by the specified array of integer coordinates, using + * the receiver's background color. The array contains alternating x and y values which are considered to represent + * points which are the vertices of the polygon. Lines are drawn between each consecutive pair, and between the + * first pair and last pair in the array. + * + * @param points an array of alternating x and y values which are the vertices of the polygon + */ + public abstract void fillPolygon(int[] points); + + /** + * Draws the closed polygon which is defined by the specified array of integer coordinates, using the receiver's + * foreground color. The array contains alternating x and y values which are considered to represent points which + * are the vertices of the polygon. Lines are drawn between each consecutive pair, and between the first pair and + * last pair in the array. + * + * @param points an array of alternating x and y values which are the vertices of the polygon + */ + public abstract void drawPolygon(int[] points); + + /** + * Fills the interior of the rectangle specified by the arguments, using the receiver's background color. + * + * @param x the x coordinate of the rectangle to be filled + * @param y the y coordinate of the rectangle to be filled + * @param width the width of the rectangle to be filled + * @param height the height of the rectangle to be filled + */ + public abstract void fillRectangle(int x, int y, int width, int height); + + /** + * Fills the interior of the specified rectangle with a gradient sweeping from left to right or top to bottom + * progressing from the graphical context gradient color to its background color. + * + * @param x the x coordinate of the rectangle to be filled + * @param y the y coordinate of the rectangle to be filled + * @param width the width of the rectangle to be filled, may be negative (inverts direction of gradient if + * horizontal) + * @param height the height of the rectangle to be filled, may be negative (inverts direction of gradient if + * horizontal) + * @param vertical if true sweeps from top to bottom, else sweeps from left to right + */ + public abstract void fillGradientRectangle(int x, int y, int width, int height, boolean vertical); + + /** + * Returns the given string width in pixels + * + * @param name the string + * @return the string width + */ + public abstract int textExtent(String name); + + /** + * Draws the given string, using the receiver's current font and foreground color. Tab expansion and carriage return + * processing are performed. If trans is true, then the background of the rectangular area where the text is being + * drawn will not be modified, otherwise it will be filled with the receiver's background color. + * + * @param string the string to be drawn + * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn + * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn + * @param trans if true the background will be transparent, otherwise it will be opaque + */ + public abstract void drawText(String string, int x, int y, boolean trans); + + /** + * Draws the given string, using the receiver's current font and foreground color. Tab expansion and carriage return + * processing are performed. The background of the rectangular area where the text is being drawn will be filled + * with the receiver's background color. + * + * @param string the string to be drawn + * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn + * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn + */ + public abstract void drawText(String string, int x, int y); + + /** + * Fills the interior of an oval, within the specified rectangular area, with the receiver's background color. + * + * @param x the x coordinate of the upper left corner of the oval to be filled + * @param y the y coordinate of the upper left corner of the oval to be filled + * @param width the width of the oval to be filled + * @param height the width of the oval to be filled + */ + public abstract void fillOval(int x, int y, int width, int height); + + /** + * Returns current the background color used in the graphical context + * + * @return the background color + */ + public abstract IColor getBackground(); + + /** + * Returns current the background color used in the graphical context + * + * @return the background color + */ + public abstract IColor getForeground(); + + /** + * Set the graphical context foreground color + * + * @param color the foreground color + */ + public abstract void setBackground(IColor color); + + /** + * Set the graphical context background color + * + * @param color the background color + */ + public abstract void setForeground(IColor color); + + /** + * Set the color to use when filling regions using gradient. The color will progess from the given color to the + * current background color + * + * @param color the gardiient color to use + */ + public abstract void setGradientColor(IColor color); + + /** + * Set the line width to use for drawing + * + * @param width the line width + */ + public abstract void setLineWidth(int width); + + /** + * Returns the current graphical context line width used for drawing + * + * @return the line width + */ + public abstract int getLineWidth(); + + /** + * Returns the LineDotD style constant + * + * @return the constant value + */ + public abstract int getLineDotStyle(); + + /** + * Returns the LineDash style constant + * + * @return the constant + */ + public abstract int getLineDashStyle(); + + /** + * Returns the LineSolid style constant + * + * @return the constant + */ + public abstract int getLineSolidStyle(); + + /** + * Draws the given string centered into the given rectangle. If the string cannot fit in the rectangle area, the + * string is truncated. If trans is true, then the background of the rectangular area where the text is being drawn + * will not be modified, otherwise it will be filled with the receiver's background color. + * + * @param name the string to draw + * @param _x the _x coordinate of the rectangle to draw the string + * @param _y the _y coordinate of the rectangle to draw the string + * @param width the width of the rectangle to draw the string + * @param height the height of the rectangle to draw the string + * @param trans if true the background will be transparent, otherwise it will be opaque + */ + public abstract void drawTextTruncatedCentred(String name, int _x, int _y, int width, int height, boolean trans); + + /** + * Draws the given string into the given rectangle (left justify) If the string cannot fit in the rectangle area, + * the string is truncated. If trans is true, then the background of the rectangular area where the text is being + * drawn will not be modified, otherwise it will be filled with the receiver's background color. + * + * @param _x the _x coordinate of the rectangle to draw the string + * @param _y the _y coordinate of the rectangle to draw the string + * @param width the width of the rectangle to draw the string + * @param height the height of the rectangle to draw the string + * @param trans if true the background will be transparent, otherwise it will be opaque + */ + public abstract void drawTextTruncated(String name, int _x, int _y, int width, int height, boolean trans); + + /** + * Copies a the source image into a (potentially different sized) rectangular area in the graphical context. If the + * source image has smaller sizes, then the source area will be stretched to fit the destination area as it is + * copied. + * + * @param image the image to draw + * @param _x the x coordinate in the destination to copy to + * @param _y the y coordinate in the destination to copy to + * @param maxWith the width in pixels of the destination rectangle + * @param maxHeight the height in pixels of the destination rectangle + */ + public abstract void drawImage(IImage image, int _x, int _y, int maxWith, int maxHeight); + + /** + * Draws the outline of a circular or elliptical arc within the specified rectangular area. The resulting arc begins + * at startAngle and extends for arcAngle degrees, using the current color. Angles are interpreted such that 0 + * degrees is at the 3 o'clock position. A positive value indicates a counter-clockwise rotation while a negative + * value indicates a clockwise rotation. The center of the arc is the center of the rectangle whose origin is (x, y) + * and whose size is specified by the width and height arguments. The resulting arc covers an area width + 1 pixels + * wide by height + 1 pixels tall. + * + * @param x the x coordinate of the upper-left corner of the arc to be drawn + * @param y the y coordinate of the upper-left corner of the arc to be drawn + * @param width the width of the arc to be drawn + * @param height the height of the arc to be drawn + * @param startAngle the beginning angle + * @param endAngle the ending angle + */ + public abstract void drawArc(int x, int y, int width, int height, int startAngle, int endAngle); + + /** + * Set the current font used in the graphical context + * + * @param font the font to use + */ + public abstract void setFont(IFont font); + + /** + * Returns the font height given font + * + * @param font + * @return the the font height + */ + public abstract int getFontHeight(IFont font); + + /** + * Returns the average character width for the given font + * + * @param font + * @return the average width + */ + public abstract int getFontWidth(IFont font); + + /** + * Creates a color with the given RGB values + * + * @param r the red component + * @param g the green component + * @param b the blue component + * @return the color + */ + public abstract IColor createColor(int r, int g, int b); + + /** + * Returns the zoom factor applied in both x and y directions when drawing + * + * @return the zoom factor + */ + public abstract float getZoom(); + + public abstract void setDrawTextWithFocusStyle(boolean focus); +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IImage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IImage.java new file mode 100755 index 0000000000..a3ff871900 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/IImage.java @@ -0,0 +1,34 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: IImage.java,v 1.2 2006/09/20 20:56:27 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings; + +/** + * @author sveyrier + * + */ +public interface IImage { + + /** + * Returns the contained image. Returned object must be an instance of org.eclipse.swt.graphics.Image if used with + * the org.eclipse.linuxtools.tmf.ui.views.uml2sd.NGC graphical context + * + * @return the color + */ + public Object getImage(); + + /** + * Disposes the image + */ + public void dispose(); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/ISDPreferences.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/ISDPreferences.java new file mode 100755 index 0000000000..47d63ffae1 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/ISDPreferences.java @@ -0,0 +1,137 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ISDPreferences.java,v 1.2 2006/09/20 20:56:27 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings; + +/** + * @author sveyrier + * + */ +public interface ISDPreferences { + + /** + * The link font with zoom preference name + */ + public static final String PREF_LINK_FONT = "PREF_LINK_FONT"; //$NON-NLS-1$ + /** + * The exclude preference time preference name + */ + public static final String PREF_EXCLUDE_EXTERNAL_TIME = "PREF_EXCLUDE_EXTERNAL_TIME"; //$NON-NLS-1$ + /** + * The use gradient color preferences name + */ + public static final String PREF_USE_GRADIENT = "PREF_USE_GRADIENT"; //$NON-NLS-1$ + /** + * The lifeline spacing width preference name + */ + public static final String PREF_LIFELINE_WIDTH = "PREF_LIFELINE_WIDTH"; //$NON-NLS-1$ + /** + * The time compression bar font preference name + */ + public static final String PREF_TIME_COMP = "PREF_TIME_COMP"; //$NON-NLS-1$ + /** + * The lifeline font preference name + */ + public static final String PREF_LIFELINE = "PREF_LIFELINE"; //$NON-NLS-1$ + /** + * The frame font preference name + */ + public static final String PREF_FRAME = "PREF_FRAME"; //$NON-NLS-1$ + /** + * The frame name font preference name + */ + public static final String PREF_FRAME_NAME = "PREF_FRAME_NAME"; //$NON-NLS-1$ + /** + * The execution occurrence font preference name + */ + public static final String PREF_EXEC = "PREF_EXEC"; //$NON-NLS-1$ + /** + * The synchronous message font preference name + */ + public static final String PREF_SYNC_MESS = "PREF_SYNC_MESS"; //$NON-NLS-1$ + /** + * The synchronous message return font preference name + */ + public static final String PREF_SYNC_MESS_RET = "PREF_SYNC_MESS_RET"; //$NON-NLS-1$ + /** + * The asynchronous message font preference name + */ + public static final String PREF_ASYNC_MESS = "PREF_ASYNC_MESS"; //$NON-NLS-1$ + /** + * The asynchronous message return font preference name + */ + public static final String PREF_ASYNC_MESS_RET = "PREF_ASYNC_MESS_RET"; //$NON-NLS-1$ + /** + * The lifeline header font (header = the always visible part of a lifeline) + */ + public static final String PREF_LIFELINE_HEADER = "PREF_LIFELINE_HEADER"; //$NON-NLS-1$ + /** + * The enable tooltip preference name + */ + public static final String PREF_TOOLTIP = "PREF_TOOLTIP"; //$NON-NLS-1$ + + /** + * Returns the background color for the given preference name (font preference name) + * + * @param prefId the preference nam + * @return the color + */ + public IColor getBackGroundColor(String prefId); + + /** + * Returns the foreground color for the given preference name (font preference name) + * + * @param prefId + * @return the color + */ + public IColor getForeGroundColor(String prefId); + + /** + * Returns the font color for the given preference name (font preference name) + * + * @param prefId + * @return the color + */ + public IColor getFontColor(String prefId); + + /** + * Returns the font for the given preference name + * + * @param prefId the preference name + * @return the font + */ + public IFont getFont(String prefId); + + /** + * Returns the time compression bar selection color + * + * @return + */ + public IColor getTimeCompressionSelectionColor(); + + /** + * Returns the background color used to draw selection + * + * @return the color + */ + public IColor getBackGroundColorSelection(); + + /** + * Returns the foreground color used to draw selection + * + * @return the color + */ + public IColor getForeGroundColorSelection(); + + public boolean useGradienColor(); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/impl/ColorImpl.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/impl/ColorImpl.java new file mode 100755 index 0000000000..1fe71f46fb --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/impl/ColorImpl.java @@ -0,0 +1,52 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ColorImpl.java,v 1.3 2008/01/24 02:28:50 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.impl; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IColor; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.widgets.Display; + +/** + * @author sveyrier + * + */ +public class ColorImpl implements IColor { + + protected Color col = null; + protected boolean manageColor = true; + + public ColorImpl(Display display, int r, int g, int b) { + col = new Color(display, r, g, b); + } + + protected ColorImpl(Color color) { + col = color; + manageColor = false; + } + + public static ColorImpl getSystemColor(int color) { + return new ColorImpl(Display.getDefault().getSystemColor(color)); + } + + @Override + public Object getColor() { + return col; + } + + @Override + public void dispose() { + if ((col != null) && (manageColor)) + col.dispose(); + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/impl/FontImpl.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/impl/FontImpl.java new file mode 100755 index 0000000000..272b6c5d4b --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/impl/FontImpl.java @@ -0,0 +1,53 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: FontImpl.java,v 1.3 2008/01/24 02:28:50 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.impl; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IFont; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.widgets.Display; + +/** + * @author sveyrier + * + */ +public class FontImpl implements IFont { + + protected Font font = null; + protected boolean manageFont = true; + + public FontImpl(Display display, FontData data) { + font = new Font(display, data); + } + + protected FontImpl(Font value) { + font = value; + manageFont = false; + } + + @Override + public Object getFont() { + return font; + } + + public static FontImpl getSystemFont() { + return new FontImpl(Display.getDefault().getSystemFont()); + } + + @Override + public void dispose() { + if (font != null) + font.dispose(); + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/impl/ImageImpl.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/impl/ImageImpl.java new file mode 100755 index 0000000000..83e4f43707 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/drawings/impl/ImageImpl.java @@ -0,0 +1,71 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ImageImpl.java,v 1.3 2008/01/24 02:28:50 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.impl; + +import java.net.URL; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IImage; +import org.eclipse.swt.graphics.Image; + +/** + * @author sveyrier + * + */ +public class ImageImpl implements IImage { + + protected Image img = null; + + public ImageImpl(String file) { + img = getResourceImage(file); + } + + public ImageImpl(Image img_) { + img = img_; + } + + protected static URL BASIC_URL = null; + static { + // URL to get images from plug-ins + // perhaps we can found better code to load images ... + // but at this time I know this: + try { + BASIC_URL = new URL("platform", "localhost", "plugin");//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } catch (Exception E) { + System.err.println(E); + } + } + + public Image getResourceImage(String _name) { + try { + // FIXME: bhufmann: don't use org.eclipse.linuxtools.tmf.ui + URL url = new URL(BASIC_URL, "plugin/org.eclipse.linuxtools.tmf.ui/icons/" + _name);//$NON-NLS-1$ + ImageDescriptor img = ImageDescriptor.createFromURL(url); + return img.createImage(); + } catch (Exception E) { + System.err.println(E); + } + return null; + } + + @Override + public Object getImage() { + return img; + } + + @Override + public void dispose() { + if (img != null) + img.dispose(); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/ConfigureMinMax.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/ConfigureMinMax.java new file mode 100755 index 0000000000..0bee489f95 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/ConfigureMinMax.java @@ -0,0 +1,44 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ConfigureMinMax.java,v 1.1 2006/08/16 21:15:28 amehregani Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.MinMaxDialog; +import org.eclipse.ui.IViewPart; + +/** + * @author sveyrier + * + */ +public class ConfigureMinMax extends Action { + + protected SDWidget viewer = null; + protected SDView view = null; + + public ConfigureMinMax(IViewPart _view) { + super(); + if (_view instanceof SDView) { + view = (SDView) _view; + } + } + + @Override + public void run() { + if (view != null && view.getSDWidget() != null) { + MinMaxDialog minMax = new MinMaxDialog(view.getSite().getShell(), view.getSDWidget()); + minMax.open(); + } + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/FirstPage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/FirstPage.java new file mode 100644 index 0000000000..535faae8eb --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/FirstPage.java @@ -0,0 +1,62 @@ +/********************************************************************** + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.linuxtools.tmf.ui.ITmfImageConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; + +/** + * Moves the focus on the first page in the sequence diagram view. + */ +public class FirstPage extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public static final String ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.firstpage"; //$NON-NLS-1$ + + protected SDView fView = null; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public FirstPage(SDView theView) { + super(); + fView = theView; + setText(SDMessages._139); + setToolTipText(SDMessages._140); + setId(ID); + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_FIRST_PAGE)); + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + if ((fView == null) || (fView.getSDWidget()) == null) { + return; + } + if (fView.getSDPagingProvider() != null) { + fView.getSDPagingProvider().firstPage(); + } + fView.updateCoolBar(); + fView.getSDWidget().redraw(); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/KeyBindingsManager.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/KeyBindingsManager.java new file mode 100644 index 0000000000..3251d1964d --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/KeyBindingsManager.java @@ -0,0 +1,241 @@ +/********************************************************************** + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.Vector; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.IHandlerActivation; +import org.eclipse.ui.handlers.IHandlerService; + +/** + * KeyBindingsManager + *

+ * Singleton class that manages key-bindings for certain commands across multiple sequence + * diagram view instances. + *

+ */ +public class KeyBindingsManager { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + // The instance + private final static KeyBindingsManager fInstance = new KeyBindingsManager(); + + // Storage of views + private Set fViews = new HashSet(); + + // Activations to store + private List fHandlerActivations = new Vector(); + + // Actions for the keys bindings + private MoveToMessage fGoToMessageForKeyBinding; + private OpenSDFindDialog fFindForKeyBinding; + private MoveSDUp fMoveUpForKeyBinding; + private MoveSDDown fMoveDownForKeyBinding; + private MoveSDLeft fMoveLeftForKeyBinding; + private MoveSDRight fMoveRightForKeyBinding; + private ShowNodeStart fShowNodeStartForKeyBinding; + private ShowNodeEnd fShowNodeEndForKeyBinding; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + private KeyBindingsManager() { + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + public static KeyBindingsManager getInstance() { + return fInstance; + } + + /** + * @param viewId Id of SD view to add and to manage + */ + public void add(String viewId) { + + if (fViews.size() == 0) { + initialize(); + } + + if(!fViews.contains(viewId)) { + fViews.add(viewId); + } + } + + /** + * @param viewId Id of SD view to remove + */ + public void remove(String viewId) { + if (fViews.contains(viewId)) { + fViews.remove(viewId); + } + if (fViews.size() == 0) { + dispose(); + } + } + + private void initialize() { + fGoToMessageForKeyBinding = new MoveToMessage(); + IHandlerService service = (IHandlerService) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(IHandlerService.class); + AbstractHandler handler = new AbstractHandler() { + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + fGoToMessageForKeyBinding.run(); + return null; + } + }; + IHandlerActivation activation = service.activateHandler(fGoToMessageForKeyBinding.getActionDefinitionId(), handler); + fHandlerActivations.add(activation); + + fMoveUpForKeyBinding = new MoveSDUp(); + handler = new AbstractHandler() { + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + fMoveUpForKeyBinding.run(); + return null; + } + }; + activation = service.activateHandler(fMoveUpForKeyBinding.getActionDefinitionId(), handler); + fHandlerActivations.add(activation); + + fMoveDownForKeyBinding = new MoveSDDown(); + handler = new AbstractHandler() { + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + fMoveDownForKeyBinding.run(); + return null; + } + }; + activation = service.activateHandler(fMoveDownForKeyBinding.getActionDefinitionId(), handler); + fHandlerActivations.add(activation); + + fMoveLeftForKeyBinding = new MoveSDLeft(); + handler = new AbstractHandler() { + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + fMoveLeftForKeyBinding.run(); + return null; + } + }; + activation = service.activateHandler(fMoveLeftForKeyBinding.getActionDefinitionId(), handler); + fHandlerActivations.add(activation); + + fMoveRightForKeyBinding = new MoveSDRight(); + handler = new AbstractHandler() { + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + fMoveRightForKeyBinding.run(); + return null; + } + }; + activation = service.activateHandler(fMoveRightForKeyBinding.getActionDefinitionId(), handler); + fHandlerActivations.add(activation); + + fFindForKeyBinding = new OpenSDFindDialog(); + handler = new AbstractHandler() { + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + fFindForKeyBinding.run(); + return null; + } + }; + activation = service.activateHandler(fFindForKeyBinding.getActionDefinitionId(), handler); + fFindForKeyBinding.setEnabled(false); + fHandlerActivations.add(activation); + + fShowNodeStartForKeyBinding = new ShowNodeStart(); + fShowNodeStartForKeyBinding.setText(SDMessages.uml_25); + + fShowNodeStartForKeyBinding.setId("org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ShowNodeStart");//$NON-NLS-1$ + fShowNodeStartForKeyBinding.setActionDefinitionId("org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ShowNodeStart");//$NON-NLS-1$ + + handler = new AbstractHandler() { + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + fShowNodeStartForKeyBinding.run(); + return null; + } + }; + activation = service.activateHandler(fShowNodeStartForKeyBinding.getActionDefinitionId(), handler); + fHandlerActivations.add(activation); + + fShowNodeEndForKeyBinding = new ShowNodeEnd(); + fShowNodeEndForKeyBinding.setText(SDMessages.uml_23); + fShowNodeEndForKeyBinding.setId("org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ShowNodeEnd");//$NON-NLS-1$ + fShowNodeEndForKeyBinding.setActionDefinitionId("org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ShowNodeEnd");//$NON-NLS-1$ + + handler = new AbstractHandler() { + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + fShowNodeEndForKeyBinding.run(); + return null; + } + }; + activation = service.activateHandler(fShowNodeEndForKeyBinding.getActionDefinitionId(), handler); + fHandlerActivations.add(activation); + + } + + private void dispose() { + IHandlerService service = (IHandlerService) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(IHandlerService.class); + for(IHandlerActivation activation : fHandlerActivations) { + service.deactivateHandler(activation); + } + + fGoToMessageForKeyBinding = null; + fFindForKeyBinding = null; + fMoveUpForKeyBinding = null; + fMoveDownForKeyBinding = null; + fMoveLeftForKeyBinding = null; + fMoveRightForKeyBinding = null; + fShowNodeStartForKeyBinding = null; + fShowNodeEndForKeyBinding = null; + } + + /** + * @param view to set in global actions + */ + public void setSdView(SDView view) { + if (fViews.size() > 0) { + fGoToMessageForKeyBinding.setView(view); + fFindForKeyBinding.setView(view); + fMoveUpForKeyBinding.setView(view); + fMoveDownForKeyBinding.setView(view); + fMoveLeftForKeyBinding.setView(view); + fMoveRightForKeyBinding.setView(view); + fShowNodeStartForKeyBinding.setView(view); + fShowNodeEndForKeyBinding.setView(view); + } + } + + /** + * Enable / disable find action + * @param enabled + */ + public void setFindEnabled(boolean enabled) { + if (fFindForKeyBinding != null) { + fFindForKeyBinding.setEnabled(enabled); + } + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/LastPage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/LastPage.java new file mode 100644 index 0000000000..d285863b8d --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/LastPage.java @@ -0,0 +1,62 @@ +/********************************************************************** + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.linuxtools.tmf.ui.ITmfImageConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; + +/** + * Moves the focus on the last page in the sequence diagram view. + */ +public class LastPage extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public static final String ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.lastpage"; //$NON-NLS-1$ + + protected SDView fView = null; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public LastPage(SDView theView) { + super(); + fView = theView; + setText(SDMessages._141); + setToolTipText(SDMessages._142); + setId(ID); + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_LAST_PAGE)); + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + if ((fView == null) || (fView.getSDWidget()) == null) { + return; + } + if (fView.getSDPagingProvider() != null) { + fView.getSDPagingProvider().lastPage(); + } + fView.updateCoolBar(); + fView.getSDWidget().redraw(); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDDown.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDDown.java new file mode 100755 index 0000000000..5faa723007 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDDown.java @@ -0,0 +1,71 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: MoveSDDown.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget; + +/** + * @author sveyrier + * + */ +public class MoveSDDown extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public final static String ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.MoveSDDown"; //$NON-NLS-1$ + + protected SDView fView = null; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public MoveSDDown() { + this(null); + } + + public MoveSDDown(SDView view) { + super(); + setId(ID); + setActionDefinitionId(ID); + fView = view; + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + if (fView == null) { + return; + } + SDWidget viewer = fView.getSDWidget(); + if (viewer != null) { + viewer.scrollBy(0, +viewer.getVisibleHeight()); + } + } + + /** + * Sets the active SD view. + * @param view The SD view. + */ + public void setView(SDView view) { + fView = view; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDLeft.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDLeft.java new file mode 100755 index 0000000000..30211b2d73 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDLeft.java @@ -0,0 +1,72 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: MoveSDLeft.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget; + +/** + * @author sveyrier + * + */ +public class MoveSDLeft extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public final static String ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.MoveSDLeft"; //$NON-NLS-1$ + + protected SDView fView = null; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public MoveSDLeft(){ + this(null); + } + + public MoveSDLeft(SDView view) { + super(); + setId(ID); + setActionDefinitionId(ID); + fView = view; + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ @Override + public void run() { + + if (fView == null) { + return; + } + + SDWidget viewer = fView.getSDWidget(); + if (viewer != null) { + viewer.scrollBy(-viewer.getVisibleWidth(), 0); + } + } + + /** + * Sets the active SD view. + * @param view The SD view. + */ + public void setView(SDView view) { + fView = view; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDRight.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDRight.java new file mode 100755 index 0000000000..b871150a34 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDRight.java @@ -0,0 +1,73 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: MoveSDRight.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget; + +/** + * @author sveyrier + * + */ + +public class MoveSDRight extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public final static String ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.MoveSDRight"; //$NON-NLS-1$ + + protected SDView fView = null; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public MoveSDRight() { + this(null); + } + + public MoveSDRight(SDView view) { + super(); + setId(ID); + setActionDefinitionId(ID); + fView = view; + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + + if (fView == null) { + return; + } + + SDWidget viewer = fView.getSDWidget(); + if (viewer != null) + viewer.scrollBy(+viewer.getVisibleWidth(), 0); + } + + /** + * Sets the active SD view. + * @param view The SD view. + */ + public void setView(SDView view) { + fView = view; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDUp.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDUp.java new file mode 100755 index 0000000000..514fc73a99 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveSDUp.java @@ -0,0 +1,71 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: MoveSDUp.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget; + +/** + * @author sveyrier + * + */ +public class MoveSDUp extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public final static String ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.MoveSDUp"; //$NON-NLS-1$ + + protected SDView fView = null; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public MoveSDUp() { + this(null); + } + + public MoveSDUp(SDView view) { + super(); + setId(ID); + setActionDefinitionId(ID); + fView = ((SDView) view); + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + if (fView == null) { + return; + } + SDWidget viewer = ((SDView) fView).getSDWidget(); + if (viewer != null) { + viewer.scrollBy(0, -viewer.getVisibleHeight()); + } + } + + /** + * Sets the active SD view. + * @param view The SD view. + */ + public void setView(SDView view) { + fView = view; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveToMessage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveToMessage.java new file mode 100755 index 0000000000..4220537629 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/MoveToMessage.java @@ -0,0 +1,116 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: MoveToMessage.java,v 1.3 2008/01/24 02:28:52 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import java.util.Iterator; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.linuxtools.tmf.ui.ITmfImageConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.BaseMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SyncMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SyncMessageReturn; + +/** + * @author sveyrier + * + */ +public class MoveToMessage extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public final static String ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.GoToMessage"; //$NON-NLS-1$ + + protected SDView fView = null; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public MoveToMessage() { + this(null); + } + + public MoveToMessage(SDView view) { + super(); + setId(ID); + setActionDefinitionId(ID); + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_SEARCH_MATCH)); + fView = view; + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + if (fView == null) { + return; + } + + SDWidget sdWidget = fView.getSDWidget(); + + if (sdWidget == null) { + return; + } + + ISelectionProvider selProvider = sdWidget.getSelectionProvider(); + ISelection sel = selProvider.getSelection(); + Object selectedNode = null; + @SuppressWarnings("unchecked") + Iterator it = ((StructuredSelection) sel).iterator(); + while (it.hasNext()) { + Object node = it.next(); + if (node instanceof BaseMessage) { + selectedNode = node; + } + } + if (selectedNode == null) + return; + + if (selectedNode instanceof SyncMessageReturn) { + GraphNode node = ((SyncMessageReturn) selectedNode).getMessage(); + sdWidget.clearSelection(); + sdWidget.addSelection(node); + sdWidget.ensureVisible(node); + // sdWidget.setFocusNode(node); + sdWidget.redraw(); + } else if (selectedNode instanceof SyncMessage) { + GraphNode node = ((SyncMessage) selectedNode).getMessageReturn(); + sdWidget.clearSelection(); + sdWidget.addSelection(node); + sdWidget.ensureVisible(node); + // sdWidget.setFocusNode(node); + sdWidget.redraw(); + } + } + + /** + * Sets the active SD view. + * @param view The SD view. + */ + public void setView(SDView view) { + fView = view; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/NextPage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/NextPage.java new file mode 100755 index 0000000000..877e585995 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/NextPage.java @@ -0,0 +1,64 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: NextPage.java,v 1.3 2008/01/24 02:28:52 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.linuxtools.tmf.ui.ITmfImageConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; + +/** + * @author sveyrier + * + */ +public class NextPage extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public static final String ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.nextpage"; //$NON-NLS-1$ + + protected SDView fView = null; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public NextPage(SDView theView) { + super(); + fView = theView; + setText(SDMessages._36); + setToolTipText(SDMessages._38); + setId(ID); + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_NEXT_PAGE)); + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + if ((fView == null) || (fView.getSDWidget()) == null) { + return; + } + if (fView.getSDPagingProvider() != null) { + fView.getSDPagingProvider().nextPage(); + } + fView.updateCoolBar(); + fView.getSDWidget().redraw(); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/OpenSDFiltersDialog.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/OpenSDFiltersDialog.java new file mode 100755 index 0000000000..b27e3c9c71 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/OpenSDFiltersDialog.java @@ -0,0 +1,66 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: OpenSDFiltersDialog.java,v 1.3 2008/01/24 02:28:52 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.linuxtools.tmf.ui.ITmfImageConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDFilterProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.FilterListDialog; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; + +/** + * Action delegate for 'Filter' on a message + */ +public class OpenSDFiltersDialog extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public static final String ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.sdFilters"; //$NON-NLS-1$ + + protected SDView fView; + protected ISDFilterProvider fProvider; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + /** + * @param view_ + */ + public OpenSDFiltersDialog(SDView view_, ISDFilterProvider provider_) { + super(SDMessages._43); + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_FILTERS)); + setId(ID); + setToolTipText(SDMessages._43); + fView = view_; + fProvider = provider_; + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + if (fView == null) { + return; + } + FilterListDialog dialog = new FilterListDialog(fView, fProvider); + dialog.open(); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/OpenSDFindDialog.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/OpenSDFindDialog.java new file mode 100755 index 0000000000..07c306fe06 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/OpenSDFindDialog.java @@ -0,0 +1,90 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: OpenSDFindDialog.java,v 1.3 2008/01/24 02:28:52 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.linuxtools.tmf.ui.ITmfImageConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.SearchFilterDialog; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; +import org.eclipse.swt.SWT; + +/** + * @author sveyrier + * + */ +public class OpenSDFindDialog extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public final static String ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.sdFind"; //$NON-NLS-1$ + public final static String ACTION_DEFINITION_ID = "org.eclipse.ui.edit.findReplace"; //$NON-NLS-1$ + + protected SDView fView = null; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public OpenSDFindDialog() { + this(null); + } + + public OpenSDFindDialog(SDView theView) { + super(SDMessages._41); + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_SEARCH_SEQ)); + setId(ID); + setActionDefinitionId(ACTION_DEFINITION_ID); + setToolTipText(SDMessages._41); + fView = theView; + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + if (fView == null) { + return; + } + + // Disable action while search is ongoing + this.setEnabled(false); + + try { + if ((fView.getExtendedFindProvider() != null) && (fView.getExtendedFindProvider().getFindAction() != null)) { + fView.getExtendedFindProvider().getFindAction().run(); + } else if (fView.getSDFindProvider() != null) { + SearchFilterDialog dialog = new SearchFilterDialog(fView, fView.getSDFindProvider(), false, SWT.NORMAL); + dialog.open(); + } + } + finally { + // Enable action after finishing the search + this.setEnabled(true); + } + } + + /** + * Sets the active SD view. + * @param view The SD view. + */ + public void setView(SDView view) { + fView = view; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/OpenSDPagesDialog.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/OpenSDPagesDialog.java new file mode 100755 index 0000000000..870acf0096 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/OpenSDPagesDialog.java @@ -0,0 +1,65 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: OpenSDPagesDialog.java,v 1.4 2008/01/24 02:28:52 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.linuxtools.tmf.ui.ITmfImageConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDAdvancedPagingProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.PagesDialog; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; + +/** + * Action delegate for 'Filter' on a message + */ +public class OpenSDPagesDialog extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public static final String ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.sdPaging"; //$NON-NLS-1$ + + protected SDView fView; + protected ISDAdvancedPagingProvider fProvider; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + /** + * @param view_ + */ + public OpenSDPagesDialog(SDView view_, ISDAdvancedPagingProvider provider_) { + super(SDMessages._44); + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_GOTO_PAGE)); + setId(ID); + fView = view_; + fProvider = provider_; + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + if (fView == null) { + return; + } + PagesDialog dialog = new PagesDialog(fView, fProvider); + dialog.open(); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/PrevPage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/PrevPage.java new file mode 100755 index 0000000000..0b26f066a8 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/PrevPage.java @@ -0,0 +1,64 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: PrevPage.java,v 1.3 2008/01/24 02:28:52 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.linuxtools.tmf.ui.ITmfImageConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; + +/** + * @author sveyrier + * + */ +public class PrevPage extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public final static String ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.prevpage"; //$NON-NLS-1$ + + protected SDView view = null; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public PrevPage(SDView theView) { + super(); + view = theView; + setText(SDMessages._35); + setToolTipText(SDMessages._37); + setId(ID); + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_PREV_PAGE)); + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + if ((view == null) || (view.getSDWidget()) == null) { + return; + } + if (view.getSDPagingProvider() != null) { + view.getSDPagingProvider().prevPage(); + } + view.updateCoolBar(); + view.getSDWidget().redraw(); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/Print.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/Print.java new file mode 100755 index 0000000000..badef00a26 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/Print.java @@ -0,0 +1,51 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: Print.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; + +public class Print extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public static final String ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.print"; //$NON-NLS-1$ + + SDView fView; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public Print(SDView view) { + super(); + setId(ID); + fView = view; + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + if (fView == null) + return; + if (fView.getSDWidget() == null) + return; + fView.getSDWidget().print(); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/ShowNodeEnd.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/ShowNodeEnd.java new file mode 100755 index 0000000000..3eec67e4f4 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/ShowNodeEnd.java @@ -0,0 +1,90 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ShowNodeEnd.java,v 1.3 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import java.util.Iterator; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.linuxtools.tmf.ui.ITmfImageConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; +import org.eclipse.ui.IViewPart; + +/** + * @author sveyrier + */ +public class ShowNodeEnd extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + protected SDView fView = null; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public ShowNodeEnd() { + this(null); + } + + public ShowNodeEnd(IViewPart _view) { + super(); + if (_view instanceof SDView) { + fView = (SDView)_view; + } + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_NODE_END)); + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + @SuppressWarnings("rawtypes") + public void run() { + if (fView == null) + return; + + SDWidget sdWidget = fView.getSDWidget(); + + if (sdWidget == null) { + return; + } + + ISelectionProvider selProvider = sdWidget.getSelectionProvider(); + ISelection sel = selProvider.getSelection(); + Object selectedNode = null; + Iterator it = ((StructuredSelection) sel).iterator(); + while (it.hasNext()) + selectedNode = it.next(); + if (selectedNode != null) { + GraphNode node = (GraphNode) selectedNode; + if ((node.getX() + node.getWidth()) * sdWidget.getZoomFactor() < sdWidget.getContentsX() + sdWidget.getVisibleWidth() / 2) + sdWidget.ensureVisible(Math.round((node.getX() + node.getWidth()) * sdWidget.getZoomFactor()) - sdWidget.getVisibleWidth() / 2, Math.round((node.getY() + node.getHeight()) * sdWidget.getZoomFactor())); + else + sdWidget.ensureVisible(Math.round((node.getX() + node.getWidth()) * sdWidget.getZoomFactor() + sdWidget.getVisibleWidth() / (float) 2), Math.round((node.getY() + node.getHeight()) * sdWidget.getZoomFactor())); + } + } + + public void setView(SDView view) { + fView = view; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/ShowNodeStart.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/ShowNodeStart.java new file mode 100755 index 0000000000..6218e8f1b5 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/ShowNodeStart.java @@ -0,0 +1,89 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ShowNodeStart.java,v 1.3 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import java.util.Iterator; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.linuxtools.tmf.ui.ITmfImageConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; +import org.eclipse.ui.IViewPart; + +/** + * @author sveyrier + */ +public class ShowNodeStart extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + protected SDView fView = null; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public ShowNodeStart() { + this(null); + } + + public ShowNodeStart(IViewPart _view) { + super(); + if (_view instanceof SDView) { + fView = (SDView)_view; + } + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_NODE_START)); + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + @SuppressWarnings("rawtypes") + public void run() { + if (fView == null) + return; + SDWidget sdWidget = fView.getSDWidget(); + + if (sdWidget == null) { + return; + } + + ISelectionProvider selProvider = sdWidget.getSelectionProvider(); + ISelection sel = selProvider.getSelection(); + Object selectedNode = null; + Iterator it = ((StructuredSelection) sel).iterator(); + while (it.hasNext()) + selectedNode = it.next(); + if (selectedNode != null) { + GraphNode node = (GraphNode) selectedNode; + if (node.getX() * sdWidget.getZoomFactor() < sdWidget.getContentsX() + sdWidget.getVisibleWidth() / 2) + sdWidget.ensureVisible(Math.round(node.getX() * sdWidget.getZoomFactor() - sdWidget.getVisibleWidth() / (float) 2), Math.round(node.getY() * sdWidget.getZoomFactor())); + else + sdWidget.ensureVisible(Math.round(node.getX() * sdWidget.getZoomFactor() + sdWidget.getVisibleWidth() / (float) 2), Math.round(node.getY() * sdWidget.getZoomFactor())); + } + } + + public void setView(SDView view) { + fView = view; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/Zoom.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/Zoom.java new file mode 100755 index 0000000000..526b82919b --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/Zoom.java @@ -0,0 +1,201 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: Zoom.java,v 1.3 2008/01/24 02:28:52 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IContributionItem; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.linuxtools.tmf.ui.ITmfImageConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IActionBars; + +/** + * @author sveyrier + * + */ +public class Zoom extends Action { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + public final static String ZOOM_IN_ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ZoomInCoolBar"; //$NON-NLS-1$ + public final static String ZOOM_OUT_ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ZoomOutCoolBar"; //$NON-NLS-1$ + public final static String RESET_ZOOM_ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.ResetZoom"; //$NON-NLS-1$ + public final static String NO_ZOOM_ID = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.NoZoom"; //$NON-NLS-1$ + + protected SDView fView = null; + protected boolean fLastZoomIn = false; + protected boolean fLastZoomOut = false; + protected boolean fLastZoom = true; + + protected static Cursor fZoomInCursor = null; + protected static Cursor fZoomOutCursor = null; + + public static enum ZoomType { + ZOOM_NONE, ZOOM_IN, ZOOM_OUT, ZOOM_RESET + }; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public Zoom(SDView _view, ZoomType type) { + super("", AS_RADIO_BUTTON);//$NON-NLS-1$ + + fView = _view; + + if ((fZoomInCursor != null) && (!fZoomInCursor.isDisposed())) { + fZoomInCursor.dispose(); + } + if ((fZoomOutCursor != null) && (!fZoomOutCursor.isDisposed())) { + fZoomOutCursor.dispose(); + } + + // Pre-create zooming cursors + fZoomInCursor = new Cursor(Display.getCurrent(), + TmfUiPlugin.getDefault().getImageFromImageRegistry(ITmfImageConstants.IMG_UI_ZOOM_IN).getImageData(), + TmfUiPlugin.getDefault().getImageFromImageRegistry(ITmfImageConstants.IMG_UI_ZOOM).getImageData(), 0, 0); + + fZoomOutCursor = new Cursor(Display.getCurrent(), + TmfUiPlugin.getDefault().getImageFromImageRegistry(ITmfImageConstants.IMG_UI_ZOOM_OUT).getImageData(), + TmfUiPlugin.getDefault().getImageFromImageRegistry(ITmfImageConstants.IMG_UI_ZOOM).getImageData(), 0, 0); + + switch (type) { + case ZOOM_IN: + setText(SDMessages._47); + setToolTipText(SDMessages._48); + setId(ZOOM_IN_ID); + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_ZOOM_IN_MENU)); + break; + + case ZOOM_OUT: + setText(SDMessages._51); + setToolTipText(SDMessages._52); + setId(ZOOM_OUT_ID); + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_ZOOM_OUT_MENU)); + break; + + case ZOOM_RESET: + setText(SDMessages._49); + setToolTipText(SDMessages._50); + setId(RESET_ZOOM_ID); + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_HOME_MENU)); + break; + + default: + // ZoomType.NONE + setText(SDMessages._53); + setToolTipText(SDMessages._54); + setId(NO_ZOOM_ID); + setImageDescriptor(TmfUiPlugin.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_SELECT_MENU)); + break; + } + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + + if ((fView == null) || (fView.getSDWidget() == null)) { + return; + } + + SDWidget viewer = fView.getSDWidget(); + + if (getId().equals(ZOOM_OUT_ID)) { + // Eclipse 3.0 M7 workaround + if (fLastZoomOut == isChecked()) { + setChecked(!isChecked()); + } + + viewer.setZoomOutMode(isChecked()); + fLastZoomOut = isChecked(); + fLastZoom = false; + if (isChecked()) { + viewer.setCursor(fZoomOutCursor); + setActionChecked(NO_ZOOM_ID, false); + } else { + viewer.setCursor(new Cursor(Display.getDefault(), SWT.CURSOR_ARROW)); + setActionChecked(NO_ZOOM_ID, true); + } + } else if (getId().equals(ZOOM_IN_ID)) { + // Eclipse 3.0 M7 workaround + if (fLastZoomIn == isChecked()) { + setChecked(!isChecked()); + } + + viewer.setZoomInMode(isChecked()); + fLastZoomIn = isChecked(); + fLastZoom = false; + if (isChecked()) { + viewer.setCursor(fZoomInCursor); + setActionChecked(NO_ZOOM_ID, false); + } else { + viewer.setCursor(new Cursor(Display.getDefault(), SWT.CURSOR_ARROW)); + setActionChecked(NO_ZOOM_ID, true); + } + } else if (getId().equals(RESET_ZOOM_ID)) { + viewer.resetZoomFactor(); + + // The reset action is a radio button only to uncheck the zoom in and out button + // when it is clicked. This avoid adding code to do it manually + // We only have to force it to false every time + fLastZoom = false; + setChecked(false); + setActionChecked(NO_ZOOM_ID, true); + } else if (getId().equals(NO_ZOOM_ID)) { + setChecked(true); + viewer.setZoomInMode(false); + viewer.setZoomInMode(false); + viewer.setCursor(new Cursor(Display.getDefault(), SWT.CURSOR_ARROW)); + } + } + + /** + * Set action check state of a view action for a given action ID. + * @param id The action ID + * @param checked true to check the action, false to uncheck the action + */ + protected void setActionChecked(String id, boolean checked) { + if (fView != null) { + IActionBars bar = fView.getViewSite().getActionBars(); + if (bar == null) { + return; + } + IToolBarManager barManager = bar.getToolBarManager(); + if (barManager == null) { + return; + } + IContributionItem nextPage = barManager.find(id); + if (nextPage != null && nextPage instanceof ActionContributionItem) { + IAction action = ((ActionContributionItem) nextPage).getAction(); + if (action != null) { + action.setChecked(checked); + } + } + } + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/IExtendedFilterProvider.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/IExtendedFilterProvider.java new file mode 100755 index 0000000000..0effcdcf86 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/IExtendedFilterProvider.java @@ -0,0 +1,25 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: IExtendedFilterProvider.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider; + +import org.eclipse.jface.action.Action; + +/** + * @author sveyrier + * + */ +public interface IExtendedFilterProvider { + + public Action getFilterAction(); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/IExtendedFindProvider.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/IExtendedFindProvider.java new file mode 100755 index 0000000000..7890adec0b --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/IExtendedFindProvider.java @@ -0,0 +1,25 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: IExtendedFindProvider.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider; + +import org.eclipse.jface.action.Action; + +/** + * @author sveyrier + * + */ +public interface IExtendedFindProvider { + + public Action getFindAction(); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/IPropertySheetPage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/IPropertySheetPage.java new file mode 100644 index 0000000000..ffba3e123b --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/IPropertySheetPage.java @@ -0,0 +1,17 @@ +/********************************************************************** + * Copyright (c) 2005, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ + +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider; + +public class IPropertySheetPage { + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDAdvancedPagingProvider.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDAdvancedPagingProvider.java new file mode 100755 index 0000000000..d241a4c485 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDAdvancedPagingProvider.java @@ -0,0 +1,42 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ISDAdvancedPagingProvider.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider; + +/** + * An advanced paging provider is able to compute number of pages, and to display the number of items it treats on each + * page and for total counts.
+ * An item can be a message, a node or anything meaningful from loader standpoint.
+ * Items are only here for information to the user. + */ +public interface ISDAdvancedPagingProvider extends ISDPagingProvider { + + /** + * @return the current page the loader is dealing with Note that first page has the 0 index (indexes are from + * 0 to pagesCount()-1). + */ + public int currentPage(); + + /** + * @return number of pages the loader is dealing with + */ + public int pagesCount(); + + /** + * Instructs a load of the <pageNumber_>th page.
+ * Note that first page has the index 0 (indexes are from 0 to pagesCount()-1). + * + * @param pageNumber_ index of the page to load + */ + public void pageNumberChanged(int pageNumber_); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDCollapseProvider.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDCollapseProvider.java new file mode 100755 index 0000000000..55ff866290 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDCollapseProvider.java @@ -0,0 +1,33 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ISDCollapseProvider.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline; + +/** + * Sequence diagram loaders which want to support Drag and Drop collapsing in the sequence diagram must implement this + * interface and register this implementation using SDViewer.setCollapsingProvider(); + * + * @author sveyrier + */ +public interface ISDCollapseProvider { + + /** + * Called back when the sequence diagram is requesting 2 lifelines collapsing + * + * @param lifeline1 - One of the lifeline to collapse + * @param Lifeline2 - The other lifeline to collapse with + */ + public void collapseTwoLifelines(Lifeline lifeline1, Lifeline lifeline2); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDExtendedActionBarProvider.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDExtendedActionBarProvider.java new file mode 100755 index 0000000000..4e4c7df8b4 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDExtendedActionBarProvider.java @@ -0,0 +1,33 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ISDExtendedActionBarProvider.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider; + +import org.eclipse.ui.IActionBars; + +/** + * Sequence Diagram loaders which implement this interface provide their own action to the action bar. + * + * Action provider are associated to a Sequence Diagram SDWidget calling SDViewer.setExtendedActionBarProvider() + * + */ +public interface ISDExtendedActionBarProvider { + + /** + * The caller is supposed to add its own actions in the cool bar and the drop-down menu.
+ * See examples in SDView.createCoolbarContent(). + * + * @param bar the bar + */ + void supplementCoolbarContent(IActionBars bar); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDFilterProvider.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDFilterProvider.java new file mode 100755 index 0000000000..0cf6f60afa --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDFilterProvider.java @@ -0,0 +1,36 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ISDFilterProvider.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider; + +import java.util.List; + +/** + * Sequence Diagram loaders which implement this class provide the actions filtering the sequence diagram.
+ * This interface also allow the implementor to set which graph nodes are supporting filtering (thanks to + * ISDGraphNodeSupporter extension).
+ * + * Action provider are associated to a Sequence Diagram SDWidget calling SDViewer.setSDFilterProvider
+ * + * Filters to be applied to be managed by the loader in an ArrayList of FilterCriteria.
+ */ +public interface ISDFilterProvider extends ISDGraphNodeSupporter { + + /** + * Called when the Filter dialog box OK button is pressed + * + * @param toApply user selection made in the dialog box + * @return true if the filter applied + */ + public boolean filter(List filters); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDFindProvider.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDFindProvider.java new file mode 100755 index 0000000000..0e9d6ff11d --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDFindProvider.java @@ -0,0 +1,41 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ISDFindProvider.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.Criteria; + +/** + * Sequence Diagram loaders which implement this class provide the actions for finding the sequence diagram. This + * interface also allow the implementor to set which action/feature are supported + * + * Action provider are associated to a Sequence Diagram SDWidget calling SDViewer.setSDFindProvider() + * + * @author sveyrier + * + */ +public interface ISDFindProvider extends ISDGraphNodeSupporter { + + /** + * Called when the Find dialog box OK button is pressed + * + * @param toApply user selection made in the dialog box + * @return true if the find got a non empty result + */ + public boolean find(Criteria toapply); + + /** + * Called when dialog is closed + */ + public void cancel(); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDGraphNodeSupporter.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDGraphNodeSupporter.java new file mode 100755 index 0000000000..879800b033 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDGraphNodeSupporter.java @@ -0,0 +1,47 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ISDGraphNodeSupporter.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider; + +/** + * Sequence Diagram loaders which implement this class provide the actions for finding or filtering the sequence + * diagram. This interface also allow the implementor to set which action/feature are supported + * + * Action provider are associated to a Sequence Diagram SDWidget calling SDViewer.setSDFindProvider() or + * SDViewer.setSDFilterProvider + * + * @author sveyrier + * + */ +public abstract interface ISDGraphNodeSupporter { + + public static int LIFELINE = 0; + public static int SYNCMESSAGE = 1; + public static int SYNCMESSAGERETURN = 2; + public static int ASYNCMESSAGE = 3; + public static int ASYNCMESSAGERETURN = 4; + public static int STOP = 5; + + /** + * Return true to enable this options, false otherwise + * + * @return true to enable this options, false otherwise + */ + public boolean isNodeSupported(int nodeType); + + /** + * Return the name to use in dialogs Not called if isNodeSupported return false + * + * @return the name to use in dialogs + */ + public String getNodeName(int nodeType, String loaderClassName); +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDPagingProvider.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDPagingProvider.java new file mode 100755 index 0000000000..6c225c892c --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDPagingProvider.java @@ -0,0 +1,58 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ISDPagingProvider.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider; + +/** + * Sequence Diagram loaders which implement this class provide the actions for sequence diagram page navigation. + * + * Action provider are associated to a Sequence Diagram SDWidget calling SDViewer.setSDPagingProvider() + * + * @author sveyrier + * + */ +public interface ISDPagingProvider { + + /** + * Return true to enable the next page button in the coolBar, false otherwise + * + * @return true if a next page exists false otherwise + */ + public boolean hasNextPage(); + + /** + * Return true to enable the previous page button in the coolBar, false otherwise + * + * @return true if a previous page exists false otherwise + */ + public boolean hasPrevPage(); + + /** + * Called back when next page button is pressed in the coolBar + */ + public void nextPage(); + + /** + * Called back when previous page button is pressed in the coolBar + */ + public void prevPage(); + + /** + * Called back when first page button is pressed in the coolBar + */ + public void firstPage(); + + /** + * Called back when last page button is pressed in the coolBar + */ + public void lastPage(); +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDPropertiesProvider.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDPropertiesProvider.java new file mode 100755 index 0000000000..46e182d7c1 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/provider/ISDPropertiesProvider.java @@ -0,0 +1,27 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: ISDPropertiesProvider.java,v 1.2 2006/09/20 20:56:26 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider; + +/** + * Contract for loaders that want to provide information in the properties view + */ +public interface ISDPropertiesProvider { + + /** + * Returns the IPropertySheetEntry that will fill in the properties view + * + * @return + */ + public IPropertySheetPage getPropertySheetEntry(); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/Criteria.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/Criteria.java new file mode 100755 index 0000000000..d78345a61a --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/Criteria.java @@ -0,0 +1,346 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: Criteria.java,v 1.2 2006/09/20 20:56:27 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.eclipse.jface.dialogs.DialogSettings; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDFilterProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDGraphNodeSupporter; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; + +/** + * This class describe the Find or Filter criteria selected by the user in the Find or Filter dialog box + * + * @author sveyrier + */ +public class Criteria { + + protected boolean lifeLineSelected = false; + protected boolean syncMessageSelected = false; + protected boolean syncMessageReturnSelected = false; + protected boolean asyncMessageSelected = false; + protected boolean asyncMessageReturnSelected = false; + private boolean caseSenstiveSelected = false; + protected boolean stopSelected = false; + private String expression = null; + private Pattern pattern = null; + + /** + * Default constructor + */ + public Criteria () { + } + + /** + * Copy constructor + * @param other Criteria to create new criteria + */ + public Criteria (Criteria other) { + this.lifeLineSelected = other.lifeLineSelected; + this.syncMessageSelected = other.syncMessageSelected; + this.syncMessageReturnSelected = other.syncMessageReturnSelected; + this.asyncMessageSelected = other.asyncMessageSelected; + this.asyncMessageReturnSelected = other.asyncMessageReturnSelected; + this.caseSenstiveSelected = other.caseSenstiveSelected; + this.stopSelected = other.stopSelected; + setExpression(other.expression); + } + + /** + * Returns true if the AsyncMessageReturn is selected, false otherwise + * + * @return true if the AsyncMessageReturn is selected, false otherwise + */ + public boolean isAsyncMessageReturnSelected() { + return asyncMessageReturnSelected; + } + + /** + * Returns true if the AsyncMessage is selected, false otherwise + * + * @return true if the AsyncMessage is selected, false otherwise + */ + public boolean isAsyncMessageSelected() { + return asyncMessageSelected; + } + + /** + * Returns the text enter by the user + * + * @return the text + */ + public String getExpression() { + return expression; + } + + /** + * @return pattern + */ + public Pattern getPattern() { + return pattern; + } + + /** + * @param pattern + */ + public void setPattern(Pattern pattern) { + this.pattern = pattern; + } + + /** + * Returns true if the LifeLine is selected, false otherwise + * + * @return true if the LifeLine is selected, false otherwise + */ + public boolean isLifeLineSelected() { + return lifeLineSelected; + } + + /** + * Returns true if the Stop is selected, false otherwise + * + * @return true if the Stop is selected, false otherwise + */ + public boolean isStopSelected() { + return stopSelected; + } + + /** + * Returns true if the SyncMessageReturn is selected, false otherwise + * + * @return true if the SyncMessageReturn is selected, false otherwise + */ + public boolean isSyncMessageReturnSelected() { + return syncMessageReturnSelected; + } + + /** + * Returns true if the SyncMessage is selected, false otherwise + * + * @return true if the SyncMessage is selected, false otherwise + */ + public boolean isSyncMessageSelected() { + return syncMessageSelected; + } + + /** + * Set AsyncMessageReturn selection state + * + * @param b true if selected, false otherwise + */ + public void setAsyncMessageReturnSelected(boolean b) { + asyncMessageReturnSelected = b; + } + + /** + * Set AsyncMessage selection state + * + * @param b true if selected, false otherwise + */ + public void setAsyncMessageSelected(boolean b) { + asyncMessageSelected = b; + } + + /** + * Set the text enter by the user + * + * @param string the text + */ + public void setExpression(String string) { + expression = string; + if (expression != null) { + + try { + if (caseSenstiveSelected) { + pattern = Pattern.compile(string); + } + else { + pattern = Pattern.compile(string, Pattern.CASE_INSENSITIVE); + } + + } catch (PatternSyntaxException e) { + pattern = null; + } + } + else { + pattern = null; + } + } + + /** + * Set Stop selection state + * + * @param b true if selected, false otherwise + */ + public void setLifeLineSelected(boolean b) { + lifeLineSelected = b; + } + + /** + * Set Stop selection state + * + * @param b true if selected, false otherwise + */ + public void setStopSelected(boolean b) { + stopSelected = b; + } + + /** + * Set SyncMessageReturn selection state + * + * @param b true if selected, false otherwise + */ + public void setSyncMessageReturnSelected(boolean b) { + syncMessageReturnSelected = b; + } + + /** + * Set SyncMessage selection state + * + * @param b true if selected, false otherwise + */ + public void setSyncMessageSelected(boolean b) { + syncMessageSelected = b; + } + + /** + * Returns true if the case sensitive is selected, false otherwise + * + * @return true if the case sensitive is selected, false otherwise + */ + public boolean isCaseSenstiveSelected() { + return caseSenstiveSelected; + } + + /** + * Set case sensitive selection state + * + * @param b true if selected, false otherwise + */ + public void setCaseSenstiveSelected(boolean b) { + caseSenstiveSelected = b; + // Make sure that pattern is set + setExpression(expression); + } + + /** + * @param to + * @return + */ + public boolean compareTo(Criteria to) { + boolean retVal = true; + if (getExpression() != null) { + retVal = getExpression().equals(to.getExpression()); + } else if (to.getExpression() != null) { + retVal = to.getExpression().equals(getExpression()); + } + return retVal && isCaseSenstiveSelected() == to.isCaseSenstiveSelected() && isAsyncMessageReturnSelected() == to.isAsyncMessageReturnSelected() && isAsyncMessageSelected() == to.isAsyncMessageSelected() + && isLifeLineSelected() == to.isLifeLineSelected() && isStopSelected() == to.isStopSelected() && isSyncMessageReturnSelected() == to.isSyncMessageReturnSelected() && isSyncMessageSelected() == to.isSyncMessageSelected(); + } + + /** + * @param settings + */ + public void save(DialogSettings settings) { + settings.put("expression", getExpression()); //$NON-NLS-1$ + settings.put("isCaseSenstiveSelected", isCaseSenstiveSelected()); //$NON-NLS-1$ + settings.put("isAsyncMessageReturnSelected", isAsyncMessageReturnSelected()); //$NON-NLS-1$ + settings.put("isAsyncMessageSelected", isAsyncMessageSelected()); //$NON-NLS-1$ + settings.put("isLifeLineSelected", isLifeLineSelected()); //$NON-NLS-1$ + settings.put("isStopSelected", isStopSelected()); //$NON-NLS-1$ + settings.put("isSyncMessageReturnSelected", isSyncMessageReturnSelected()); //$NON-NLS-1$ + settings.put("isSyncMessageSelected", isSyncMessageSelected()); //$NON-NLS-1$ + } + + /** + * @param settings + */ + public void load(DialogSettings settings) { + setExpression(settings.get("expression")); //$NON-NLS-1$ + setCaseSenstiveSelected(settings.getBoolean("isCaseSenstiveSelected")); //$NON-NLS-1$ + setAsyncMessageReturnSelected(settings.getBoolean("isAsyncMessageReturnSelected")); //$NON-NLS-1$ + setAsyncMessageSelected(settings.getBoolean("isAsyncMessageSelected")); //$NON-NLS-1$ + setLifeLineSelected(settings.getBoolean("isLifeLineSelected")); //$NON-NLS-1$ + setStopSelected(settings.getBoolean("isStopSelected")); //$NON-NLS-1$ + setSyncMessageReturnSelected(settings.getBoolean("isSyncMessageReturnSelected")); //$NON-NLS-1$ + setSyncMessageSelected(settings.getBoolean("isSyncMessageSelected")); //$NON-NLS-1$ + } + + public String getGraphNodeSummary(ISDFilterProvider provider, String loaderClassName) { + ArrayList list = new ArrayList(); + + if (provider != null) { + if (isLifeLineSelected()) { + list.add(provider.getNodeName(ISDGraphNodeSupporter.LIFELINE, loaderClassName)); + } + if (isSyncMessageSelected()) { + list.add(provider.getNodeName(ISDGraphNodeSupporter.SYNCMESSAGE, loaderClassName)); + } + if (isSyncMessageReturnSelected()) { + list.add(provider.getNodeName(ISDGraphNodeSupporter.SYNCMESSAGERETURN, loaderClassName)); + } + if (isAsyncMessageSelected()) { + list.add(provider.getNodeName(ISDGraphNodeSupporter.ASYNCMESSAGE, loaderClassName)); + } + if (isAsyncMessageReturnSelected()) { + list.add(provider.getNodeName(ISDGraphNodeSupporter.ASYNCMESSAGERETURN, loaderClassName)); + } + if (isStopSelected()) { + list.add(provider.getNodeName(ISDGraphNodeSupporter.STOP, loaderClassName)); + } + } else { + if (isLifeLineSelected()) { + list.add(SDMessages._28); + } + if (isSyncMessageSelected()) { + list.add(SDMessages._30); + } + if (isSyncMessageReturnSelected()) { + list.add(SDMessages._31); + } + if (isAsyncMessageSelected()) { + list.add(SDMessages._32); + } + if (isAsyncMessageReturnSelected()) { + list.add(SDMessages._33); + } + if (isStopSelected()) { + list.add(SDMessages._29); + } + } + + String ret = "", prefix = "["; //$NON-NLS-1$ //$NON-NLS-2$ + for (Iterator i = list.iterator(); i.hasNext();) { + String s = (String) i.next(); + ret += prefix + s; + prefix = " " + SDMessages._34 + " "; //$NON-NLS-1$ //$NON-NLS-2$ + } + ret += "]"; //$NON-NLS-1$ + return ret; + } + + /** + * @param stringToMatch + * @return true if string matches expression + */ + public boolean matches(String stringToMatch) { + if (pattern == null) { + return false; + } + return pattern.matcher(stringToMatch).matches(); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/FilterCriteria.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/FilterCriteria.java new file mode 100755 index 0000000000..9bcadcc3bd --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/FilterCriteria.java @@ -0,0 +1,187 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: FilterCriteria.java,v 1.3 2008/01/24 02:29:09 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets; + +import java.util.Iterator; +import java.util.List; + +import org.eclipse.jface.dialogs.DialogSettings; + +/** + * A filter criteria is a criteria that can be activated or not, positive or not. + */ +public class FilterCriteria { + + protected static final String ACTIVE = "active"; //$NON-NLS-1$ + protected static final String POSITIVE = "positive"; //$NON-NLS-1$ + protected static final String LOADERCLASSNAME = "loaderClassName"; //$NON-NLS-1$ + + protected Criteria criteria; + protected boolean active; + protected boolean positive; + protected String loaderClassName; + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(super.toString()); + sb.append(":"); //$NON-NLS-1$ + if (criteria != null) { + sb.append(" expression=");sb.append(criteria.getExpression()); //$NON-NLS-1$ + sb.append(" active=");sb.append(active); //$NON-NLS-1$ + sb.append(" positive=");sb.append(positive); //$NON-NLS-1$ + } else { + sb.append("empty criteria"); //$NON-NLS-1$ + } + return sb.toString(); + } + + /** + * @param criteria_ + * @param active_ + * @param positive_ + */ + public FilterCriteria(Criteria criteria_, boolean active_, boolean positive_) { + setCriteria(criteria_); + setActive(active_); + setPositive(positive_); + } + + /** + * @param criteria_ + * @param active_ + * @param positive_ + * @param loaderClassName_ + */ + public FilterCriteria(Criteria criteria_, boolean active_, boolean positive_, String loaderClassName_) { + setCriteria(criteria_); + setActive(active_); + setPositive(positive_); + setLoaderClassName(loaderClassName_); + } + + /** + * Copy Constructor + * @param other FilterCriteria + */ + public FilterCriteria (FilterCriteria other) { + criteria = new Criteria(other.criteria); + this.active = other.active; + this.positive = other.positive; + this.loaderClassName = other.loaderClassName; + } + + protected FilterCriteria() { + } + + /** + * @param criteria_ + */ + public void setCriteria(Criteria criteria_) { + criteria = criteria_; + } + + public Criteria getCriteria() { + return criteria; + } + + /** + * @param active_ + */ + public void setActive(boolean active_) { + active = active_; + } + + public boolean isActive() { + return active; + } + + /** + * @param positive The positive to set. + */ + public void setPositive(boolean positive_) { + positive = positive_; + } + + /** + * @return Returns the positive. + */ + public boolean isPositive() { + return positive; + } + + /** + * @return Returns the class loader name. + */ + public void setLoaderClassName(String loaderClassName_) { + loaderClassName = loaderClassName_; + } + + /** + * @return Returns the class loader name. + */ + public String getLoaderClassName() { + return loaderClassName; + } + + public static FilterCriteria find(FilterCriteria what, List list) { + if (what != null && list != null) { + try { + for (Iterator i = list.iterator(); i.hasNext();) { + FilterCriteria fc = (FilterCriteria) i.next(); + if (what.compareTo(fc)) { + return fc; + } + } + } catch (Exception e) { + // Silence + } + } + return null; + } + + public boolean compareTo(FilterCriteria to) { + if (isPositive() == to.isPositive() && getCriteria().compareTo(to.getCriteria())) { + if (getLoaderClassName() == null && to.getLoaderClassName() == null) { + return true; + } + if ((getLoaderClassName() != null && to.getLoaderClassName() != null) && getLoaderClassName().equals(to.getLoaderClassName())) { + return true; + } + } + return false; + } + + public void save(DialogSettings settings) { + settings.put(ACTIVE, isActive()); + settings.put(POSITIVE, isPositive()); + if (getLoaderClassName() != null) { + settings.put(LOADERCLASSNAME, getLoaderClassName()); + } else { + settings.put(LOADERCLASSNAME, ""); //$NON-NLS-1$ + } + if (criteria != null) + criteria.save(settings); + } + + /** + * @param settings + */ + public void load(DialogSettings settings) { + setActive(settings.getBoolean(ACTIVE)); + setPositive(settings.getBoolean(POSITIVE)); + String loaderClassName_ = settings.get(LOADERCLASSNAME); + setLoaderClassName(loaderClassName_ != null && loaderClassName_.length() > 0 ? loaderClassName_ : null); + if (criteria != null) + criteria.load(settings); + } +} \ No newline at end of file diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/FilterListDialog.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/FilterListDialog.java new file mode 100755 index 0000000000..88d53b584a --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/FilterListDialog.java @@ -0,0 +1,443 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: FilterListDialog.java,v 1.4 2008/01/24 02:29:09 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.DialogSettings; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDFilterProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.RowData; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.ui.IViewPart; + +/** + * This is the filters list dialog.
+ * It is associated to an SDView and to a ISDFilterProvider.
+ */ +public class FilterListDialog extends Dialog { + + protected static final String FILTERS_LIST_CRITERIA = "filtersListsCriteria"; //$NON-NLS-1$ + protected static final String FILTERS_LIST_SIZE = "filtersListSize"; //$NON-NLS-1$ + + /** + * viewer and provided are kept here as attributes + */ + protected IViewPart viewer = null; + protected ISDFilterProvider provider = null; + + /** + * filters are the result of editing this list + */ + protected List filters; + + /** + * add, remove and edit buttons + */ + protected Button add, remove, edit; + + /** + * table + */ + protected Table table; + + /** + * A class to map TableItems that can be toggled active or inactive and Criterias + */ + protected class CriteriaTableItem { + + protected Criteria criteria; + protected boolean positive; + protected String loaderClassName; + protected TableItem tableItem; + + public CriteriaTableItem(Table parent, boolean checked_, boolean positive_, String loaderClassName_) { + tableItem = new TableItem(parent, SWT.NONE); + tableItem.setData(this); + tableItem.setChecked(checked_); + positive = positive_; + loaderClassName = loaderClassName_; + } + + public CriteriaTableItem(Table parent, boolean checked_, boolean positive_, String loaderClassName_, int index) { + tableItem = new TableItem(parent, SWT.NONE, index); + tableItem.setChecked(checked_); + positive = positive_; + loaderClassName = loaderClassName_; + } + + public void setCriteria(Criteria criteria_) { + criteria = criteria_; + tableItem.setText((positive ? SDMessages._59 : SDMessages._58) + " " + criteria.getExpression() + " " + criteria.getGraphNodeSummary(provider, loaderClassName)); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public Criteria getCriteria() { + return criteria; + } + + public boolean getPositive() { + return positive; + } + + public String getLoaderClassName() { + return loaderClassName; + } + } + + /** + * @param c + * @param checked + */ + protected void addCriteria(Criteria c, boolean checked, boolean positive, String loaderClassName) { + CriteriaTableItem cti = new CriteriaTableItem(table, checked, positive, loaderClassName); + cti.setCriteria(c); + } + + /** + * @param new_ + */ + protected void replaceSelectedCriteria(Criteria new_) { + CriteriaTableItem cti = (CriteriaTableItem) table.getSelection()[0].getData(); + cti.setCriteria(new_); + } + + /** + * + */ + protected void handleTableSelectionCount() { + int count = table.getSelectionCount(); + edit.setEnabled(count == 1); + remove.setEnabled(count > 0); + } + + /** + * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) + */ + @Override + public Control createDialogArea(Composite parent) { + + Group ret = new Group(parent, SWT.NONE); + ret.setText(SDMessages._57); + RowLayout rowLayout = new RowLayout(); + rowLayout.wrap = false; + rowLayout.pack = true; + rowLayout.justify = false; + rowLayout.type = SWT.HORIZONTAL; + rowLayout.marginLeft = 4; + rowLayout.marginTop = 4; + rowLayout.marginRight = 4; + rowLayout.marginBottom = 4; + rowLayout.spacing = 8; + ret.setLayout(rowLayout); + + table = new Table(ret, SWT.MULTI | SWT.CHECK); + table.setLayoutData(new RowData(220, 84)); + table.setHeaderVisible(false); + table.addSelectionListener(new SelectionListener() { + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent) + */ + @Override + public void widgetDefaultSelected(SelectionEvent e) { + int count = table.getSelectionCount(); + if (count == 1) { + Criteria c = openFilterDialog(((CriteriaTableItem) table.getSelection()[0].getData()).getCriteria(), SDMessages._63); + if (c != null) { + replaceSelectedCriteria(c); + } + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent) + */ + @Override + public void widgetSelected(SelectionEvent e) { + handleTableSelectionCount(); + } + }); + if (filters != null) { + for (Iterator i = filters.iterator(); i.hasNext();) { + FilterCriteria filterCriteria = (FilterCriteria) i.next(); + addCriteria(filterCriteria.getCriteria(), filterCriteria.isActive(), filterCriteria.isPositive(), filterCriteria.getLoaderClassName()); + } + } + + Composite commands = new Composite(ret, SWT.NONE); + RowLayout rowLayoutCommands = new RowLayout(); + rowLayoutCommands.wrap = false; + rowLayoutCommands.pack = false; + rowLayoutCommands.justify = true; + rowLayoutCommands.type = SWT.VERTICAL; + rowLayoutCommands.marginLeft = 0; + rowLayoutCommands.marginTop = 4; + rowLayoutCommands.marginRight = 0; + rowLayoutCommands.marginBottom = 4; + rowLayoutCommands.spacing = 8; + commands.setLayout(rowLayoutCommands); + add = new Button(commands, SWT.NONE); + add.setText(SDMessages._61); + add.addSelectionListener(new SelectionListener() { + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent) + */ + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Nothing to do + } + + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent) + */ + @Override + public void widgetSelected(SelectionEvent e) { + Criteria init = new Criteria(); + Criteria c = openFilterDialog(init, SDMessages._62); + if (c != null) { + addCriteria(c, true, false, null); + } + } + }); + + edit = new Button(commands, SWT.NONE); + edit.setText(SDMessages._60); + edit.addSelectionListener(new SelectionListener() { + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent) + */ + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Nothing to do + } + + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent) + */ + @Override + public void widgetSelected(SelectionEvent e) { + Criteria c = openFilterDialog(((CriteriaTableItem) table.getSelection()[0].getData()).getCriteria(), SDMessages._63); + if (c != null) { + replaceSelectedCriteria(c); + } + } + }); + edit.setEnabled(false); + + remove = new Button(commands, SWT.NONE); + remove.setText(SDMessages._64); + remove.addSelectionListener(new SelectionListener() { + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent) + */ + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Nothing to do + } + + /* + * (non-Javadoc) + * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent) + */ + @Override + public void widgetSelected(SelectionEvent e) { + table.remove(table.getSelectionIndices()); + handleTableSelectionCount(); + } + }); + remove.setEnabled(false); + + getShell().setText(SDMessages._65); + /* + * for (int i=0;i 0) { + filters = new ArrayList(); + } else { + filters = null; + } + for (int i = 0; i < table.getItemCount(); i++) { + TableItem item = table.getItem(i); + CriteriaTableItem cti = (CriteriaTableItem) item.getData(); + FilterCriteria fc = new FilterCriteria(cti.getCriteria(), item.getChecked(), cti.getPositive(), cti.getLoaderClassName()); + FilterCriteria efc = FilterCriteria.find(fc, filters); + if (efc == null) { + filters.add(fc); + } else { + efc.setActive(efc.isActive() || fc.isActive()); + } + } + super.close(); + provider.filter(filters); + saveFiltersCriteria(filters); + } + + /** + * @param filters_ + */ + public void setFilters(ArrayList filters_) { + filters = filters_; + } + + /** + * @return the filters list after editing + */ + public List getFilters() { + return filters; + } + + protected void loadFiltersCriteria() { + List globalFilters = getGlobalFilters(); + for (Iterator i = globalFilters.iterator(); i.hasNext();) { + FilterCriteria filterCriteria = (FilterCriteria) i.next(); + addCriteria(filterCriteria.getCriteria(), filterCriteria.isActive(), filterCriteria.isPositive(), filterCriteria.getLoaderClassName()); + } + } + + public static List getGlobalFilters() { + DialogSettings settings = (DialogSettings) TmfUiPlugin.getDefault().getDialogSettings().getSection(FILTERS_LIST_CRITERIA); + int i = 0; + DialogSettings section = null; + int size = 0; + if (settings != null) { + try { + size = settings.getInt(FILTERS_LIST_SIZE); + } catch (NumberFormatException e) { + // This is not a problem + size = 0; + } + section = (DialogSettings) settings.getSection(FILTERS_LIST_CRITERIA + i); + } + + List globalFilters = new ArrayList(); + + while ((section != null) && (i < size)) { + FilterCriteria criteria = new FilterCriteria(); + criteria.setCriteria(new Criteria()); + criteria.load(section); + globalFilters.add(criteria); + section = (DialogSettings) settings.getSection(FILTERS_LIST_CRITERIA + (++i)); + } + + return globalFilters; + } + + public static void saveFiltersCriteria(List globalFilters) { + DialogSettings settings = (DialogSettings) TmfUiPlugin.getDefault().getDialogSettings(); + DialogSettings section = (DialogSettings) settings.getSection(FILTERS_LIST_CRITERIA); + if (section == null) { + section = (DialogSettings) settings.addNewSection(FILTERS_LIST_CRITERIA); + } + + if (globalFilters == null) { + section.put(FILTERS_LIST_SIZE, 0); + return; + } + + section.put(FILTERS_LIST_SIZE, globalFilters.size()); + + FilterCriteria criteria; + + for (int j = 0; j < globalFilters.size(); j++) { + if (!(globalFilters.get(j) instanceof FilterCriteria)) + return; + + criteria = (FilterCriteria) globalFilters.get(j); + DialogSettings subSection = (DialogSettings) section.getSection(FILTERS_LIST_CRITERIA + j); + ; + if (subSection == null) { + subSection = (DialogSettings) section.addNewSection(FILTERS_LIST_CRITERIA + j); + } + criteria.save(subSection); + } + } + + public static void deactivateSavedGlobalFilters() { + // Deactivate all filters + List filters = getGlobalFilters(); + for(FilterCriteria criteria : filters) { + criteria.setActive(false); + } + // Save settings + FilterListDialog.saveFiltersCriteria(filters); + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/PagesDialog.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/PagesDialog.java new file mode 100755 index 0000000000..41b4acbddd --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/PagesDialog.java @@ -0,0 +1,150 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: PagesDialog.java,v 1.4 2008/01/24 02:29:09 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets; + +import java.text.MessageFormat; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDAdvancedPagingProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IViewPart; + +/** + * This is the filters list dialog.
+ * It is associated to an SDView and to a ISDFilterProvider.
+ */ +public class PagesDialog extends Dialog { + + /** + * viewer and provided are kept here as attributes + */ + protected ISDAdvancedPagingProvider provider = null; + protected TextArea currentPage; + protected Label totalPageComment; + + /** + * This is a Text Control that accepts only digits and ensures that bounds are respected + */ + protected static class TextArea { + + protected Text text; + int min, max; + + public TextArea(Composite parent) { + text = new Text(parent, SWT.SINGLE | SWT.BORDER | SWT.RIGHT); + text.setTextLimit(10); + } + + public void setValue(int v) { + int value = Math.max(min, Math.min(max, v)); + text.setText(Integer.toString(value)); + } + + public int getValue() { + int res; + try { + res = Integer.parseInt(text.getText()); + } catch (Exception e) { + // ignored + res = 0; + } + return Math.max(min, Math.min(max, res)); + } + + public void setBounds(int min_, int max_) { + min = Math.max(0, min_); + max = Math.max(min, max_); + Integer tab[] = new Integer[2]; + tab[0] = Integer.valueOf(min); + tab[1] = Integer.valueOf(max); + text.setToolTipText(MessageFormat.format(SDMessages._69, (Object[]) tab)); + } + } + + /** + * + */ + protected void updateComments() { + int pages = Math.max(0, provider.pagesCount()); + String totalPageCommentText = SDMessages._70 + pages + " "; //$NON-NLS-1$ + if (pages == 0) { + totalPageCommentText += SDMessages._71; + } else if (pages == 1) { + totalPageCommentText += SDMessages._72; + } else { + totalPageCommentText += SDMessages._73; + } + totalPageComment.setText(totalPageCommentText); + } + + /** + * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) + */ + @Override + public Control createDialogArea(Composite parent) { + + Group ret = new Group(parent, SWT.NONE); + GridData data = new GridData(); + data.grabExcessHorizontalSpace = true; + data.horizontalAlignment = GridData.FILL; + ret.setLayoutData(data); + ret.setText(SDMessages._67); + + FillLayout fillLayout = new FillLayout(SWT.VERTICAL); + ret.setLayout(fillLayout); + + Label label = new Label(ret, SWT.NONE); + label.setText(SDMessages._75); + + currentPage = new TextArea(ret); + currentPage.setBounds(1, provider.pagesCount()); + currentPage.setValue(provider.currentPage() + 1); + + totalPageComment = new Label(ret, SWT.NONE); + totalPageComment.setAlignment(SWT.RIGHT); + + updateComments(); + + getShell().setText(SDMessages._68); + return ret; + } + + /** + * @param view_ + * @param loader_ + */ + public PagesDialog(IViewPart view_, ISDAdvancedPagingProvider loader_) { + super(view_.getSite().getShell()); + provider = loader_; + setShellStyle(SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL); + } + + /** + * Called when the dialog box ok button is pressed + */ + @Override + public void okPressed() { + int currentPageValue = currentPage.getValue() - 1; + super.close(); + provider.pageNumberChanged(currentPageValue); + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/SearchFilterDialog.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/SearchFilterDialog.java new file mode 100755 index 0000000000..0a5f1699eb --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/SearchFilterDialog.java @@ -0,0 +1,350 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: SearchFilterDialog.java,v 1.4 2008/01/24 02:29:09 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.DialogSettings; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.AsyncMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.AsyncMessageReturn; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Stop; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SyncMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SyncMessageReturn; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDFindProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDGraphNodeSupporter; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.TabFolder; + +/** + * This is the common dialog to define Find and/or Filter Criteria(s) + */ +public class SearchFilterDialog extends Dialog { + + protected static final String FIND_CRITERIA = "findCriteria"; //$NON-NLS-1$ + protected static final String FIND_EXPRESSION_LIST = "findExpressionList"; //$NON-NLS-1$ + protected static final String FILTER_CRITERIA = "filterCriteria"; //$NON-NLS-1$ + protected static final String FILTER_EXPRESSION_LIST = "filterExpressionList"; //$NON-NLS-1$ + protected static final int MAX_EXPRESSION_LIST = 7; + + /** + * viewer + */ + protected SDView viewer = null; + + /** + * tab with the controls for a Criteria + */ + protected TabFolder tab = null; + + /** + * Criteria updated by this dialog + */ + protected Criteria criteria = null; + + /** + * find/filter provider telling which graph nodes are supported + */ + protected ISDGraphNodeSupporter provider = null; + + /** + * okText is the text for the Ok button and title is the title of the dialog.
+ * Both depend on the usage that is done of this dialog (find or filter). + */ + protected String okText, title; + + /** + * List of string expressions that have been searched already + */ + protected String[] expressionList; + + /** + * find is true if the dialog is for the find feature and false for filter feature + */ + protected boolean find; + + /* + * (non-Javadoc) + * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) + */ + @Override + public Control createDialogArea(Composite arg0) { + if (find) + expressionList = TmfUiPlugin.getDefault().getDialogSettings().getArray(FIND_EXPRESSION_LIST); + else + expressionList = TmfUiPlugin.getDefault().getDialogSettings().getArray(FILTER_EXPRESSION_LIST); + if (expressionList == null) { + expressionList = new String[0]; + } + return new TabContents(arg0, provider, getButton(IDialogConstants.OK_ID), expressionList); + } + + /** + * @param theView + * @param _provider + */ + public SearchFilterDialog(SDView view_, ISDGraphNodeSupporter provider_, boolean filter_, int style) { + super(view_.getSDWidget().getShell()); + setShellStyle(SWT.DIALOG_TRIM | style); + provider = provider_; + viewer = view_; + find = !filter_; + } + + /** + * Open the dialog box + */ + @Override + public int open() { + create(); + + if (criteria == null) + loadCriteria(); + if (criteria == null) { + criteria = new Criteria(); + criteria.setLifeLineSelected(provider.isNodeSupported(ISDGraphNodeSupporter.LIFELINE)); + criteria.setSyncMessageSelected(provider.isNodeSupported(ISDGraphNodeSupporter.SYNCMESSAGE)); + criteria.setSyncMessageReturnSelected(provider.isNodeSupported(ISDGraphNodeSupporter.SYNCMESSAGERETURN)); + criteria.setAsyncMessageSelected(provider.isNodeSupported(ISDGraphNodeSupporter.ASYNCMESSAGE)); + criteria.setAsyncMessageReturnSelected(provider.isNodeSupported(ISDGraphNodeSupporter.ASYNCMESSAGERETURN)); + criteria.setStopSelected(provider.isNodeSupported(ISDGraphNodeSupporter.STOP)); + } + copyFromCriteria(criteria); + + if (okText != null) { + getButton(IDialogConstants.OK_ID).setText(okText); + } else { + getButton(IDialogConstants.OK_ID).setText(SDMessages._21); + } + + if (find) { + getButton(IDialogConstants.CANCEL_ID).setText(SDMessages._22); + } + + Button okButton = getButton(IDialogConstants.OK_ID); + ((TabContents) getDialogArea()).setOkButton(okButton); + if (criteria == null || !((criteria.getExpression() != null && !criteria.getExpression().equals("")) && //$NON-NLS-1$ + (criteria.isAsyncMessageReturnSelected() || criteria.isAsyncMessageSelected() || criteria.isLifeLineSelected() || criteria.isStopSelected() || criteria.isSyncMessageReturnSelected() || criteria.isSyncMessageSelected()))) { + okButton.setEnabled(false); + } + + if (title != null) { + getShell().setText(title); + } else { + getShell().setText(SDMessages._24); + } + + getShell().pack(); + getShell().setLocation(getShell().getDisplay().getCursorLocation()); + + criteria = null; + return super.open(); + } + + /** + * + */ + @SuppressWarnings("rawtypes") + protected void loadCriteria() { + + String CRITERIA = FIND_CRITERIA; + if (!find) + CRITERIA = FILTER_CRITERIA; + + DialogSettings section = (DialogSettings) TmfUiPlugin.getDefault().getDialogSettings().getSection(CRITERIA); + List selection = viewer.getSDWidget().getSelection(); + if ((selection == null || selection.size() != 1) || (!find)) { + if (section != null) { + criteria = new Criteria(); + criteria.load(section); + } + } else { + GraphNode gn = (GraphNode) selection.get(0); + criteria = new Criteria(); + criteria.setExpression(gn.getName()); + criteria.setCaseSenstiveSelected(true); + if (gn instanceof Lifeline && provider.isNodeSupported(ISDGraphNodeSupporter.LIFELINE)) { + criteria.setLifeLineSelected(true); + } else if (gn instanceof SyncMessageReturn && provider.isNodeSupported(ISDGraphNodeSupporter.SYNCMESSAGERETURN)) { + criteria.setSyncMessageReturnSelected(true); + } else if ((gn instanceof SyncMessageReturn || gn instanceof SyncMessage) && provider.isNodeSupported(ISDGraphNodeSupporter.SYNCMESSAGE)) { + criteria.setSyncMessageSelected(true); + } else if (gn instanceof AsyncMessageReturn && provider.isNodeSupported(ISDGraphNodeSupporter.ASYNCMESSAGERETURN)) { + criteria.setAsyncMessageReturnSelected(true); + } else if ((gn instanceof AsyncMessageReturn || gn instanceof AsyncMessage) && provider.isNodeSupported(ISDGraphNodeSupporter.ASYNCMESSAGE)) { + criteria.setAsyncMessageSelected(true); + } else if (gn instanceof Stop && provider.isNodeSupported(ISDGraphNodeSupporter.STOP)) { + criteria.setStopSelected(true); + } + } + } + + /** + * Called when the dialog box ok button is pressed and call back the appropriate action provider (ISDFilterProvider + * or ISDFindProvider) + */ + @Override + public void okPressed() { + copyToCriteria(); + if (!find) { + saveCriteria(); + super.close(); // Filter is modal + } + if ((provider != null) && (provider instanceof ISDFindProvider) && find) { + boolean result = ((ISDFindProvider) provider).find(criteria); + TabContents content = getTabContents(); + content.setResult(result); + } + } + + /** + * + */ + @Override + public void cancelPressed() { + if (find) { + copyToCriteria(); + if (provider instanceof ISDFindProvider) { + ((ISDFindProvider) provider).cancel(); + } + saveCriteria(); + } + super.cancelPressed(); + } + + /** + * + */ + public void saveCriteria() { + String CRITERIA = FIND_CRITERIA; + String EXPRESSION_LIST = FIND_EXPRESSION_LIST; + if (!find) { + CRITERIA = FILTER_CRITERIA; + EXPRESSION_LIST = FILTER_EXPRESSION_LIST; + } + DialogSettings settings = (DialogSettings) TmfUiPlugin.getDefault().getDialogSettings(); + DialogSettings section = (DialogSettings) settings.getSection(CRITERIA); + if (section == null) { + section = (DialogSettings) settings.addNewSection(CRITERIA); + } + criteria.save(section); + + if (criteria.getExpression().length() > 0) { + ArrayList list = new ArrayList(); + for (int i = 0; i < expressionList.length; i++) { + list.add(expressionList[i]); + } + // Remove the used expression if one from the dropdown list + list.remove(criteria.getExpression()); + // Put the new expression at the beginning + list.add(0, criteria.getExpression()); + // Fill in the expressionList, truncating to MAX_EXPRESSION_LIST + int size = Math.min(list.size(), MAX_EXPRESSION_LIST); + String[] temp = new String[size]; + for (int i = 0; i < size; i++) { + temp[i] = (String) list.get(i); + } + expressionList = temp; + settings.put(EXPRESSION_LIST, expressionList); + } + } + + /** + * + */ + public Criteria getCriteria() { + return criteria; + } + + /** + * @param criteria_ + */ + public void setCriteria(Criteria criteria_) { + criteria = criteria_; + } + + /** + * Get the current end-user settings from the dialog to a Criteria + * + * @param content + */ + public void copyToCriteria() { + criteria = new Criteria(); + TabContents content = getTabContents(); + criteria.setLifeLineSelected(content.getLifelineButtonSelection()); + criteria.setSyncMessageSelected(content.getSynMessageButtonSelection()); + criteria.setSyncMessageReturnSelected(content.getSynMessageReturnButtonSelection()); + criteria.setAsyncMessageSelected(content.getAsynMessageButtonSelection()); + criteria.setAsyncMessageReturnSelected(content.getAsynMessageReturnButtonSelection()); + criteria.setStopSelected(content.getStopButtonSelection()); + criteria.setCaseSenstiveSelected(content.getCaseSensitiveSelection()); + criteria.setExpression(content.getSearchText()); + } + + /** + * @return + * @todo Generated comment + */ + protected TabContents getTabContents() { + TabContents content = null; + if (tab == null) + content = (TabContents) getDialogArea(); + else + content = (TabContents) tab.getSelection()[0].getControl(); + return content; + } + + /** + * Initialize the dialog with the settings of an existing Criteria
+ * Criteria must not be null and the TabContents must have been created + * + * @param from + */ + public void copyFromCriteria(Criteria from) { + TabContents content = getTabContents(); + content.setLifelineButtonSelection(from.isLifeLineSelected()); + content.setSynMessageButtonSelection(from.isSyncMessageSelected()); + content.setSynMessageReturnButtonSelection(from.isSyncMessageReturnSelected()); + content.setAsynMessageButtonSelection(from.isAsyncMessageSelected()); + content.setAsynMessageReturnButtonSelection(from.isSyncMessageReturnSelected()); + content.setStopButtonSelection(from.isStopSelected()); + content.setCaseSensitiveSelection(from.isCaseSenstiveSelected()); + if (from.getExpression() != null) { + content.setSearchText(from.getExpression()); + } + } + + /** + * @param okText_ + */ + public void setOkText(String okText_) { + okText = okText_; + } + + /** + * @param title_ + */ + public void setTitle(String title_) { + title = title_; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/TabContents.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/TabContents.java new file mode 100755 index 0000000000..d7802004ac --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/handlers/widgets/TabContents.java @@ -0,0 +1,399 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: TabContents.java,v 1.3 2008/01/24 02:29:09 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDGraphNodeSupporter; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; + +/** + * Here are the controls that allows to create or update a find or filter Criteria. + */ +public class TabContents extends Composite { + + protected class GraphNodeTypeListener implements SelectionListener { + /** + * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent) + */ + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Nothing to do + } + + /** + * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent) + */ + @Override + public void widgetSelected(SelectionEvent e) { + updateOkButton(); + } + + } + + protected class ExpressionListener implements ModifyListener { + /** + * @see org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent) + */ + @Override + public void modifyText(ModifyEvent e) { + // System.err.println("modifyText: "+e.getSource()); + updateOkButton(); + } + } + + GraphNodeTypeListener graphNodeTypeListener = null; + ExpressionListener expressionListener = null; + + Button lifelineButton = null; + Button stopButton = null; + Button synMessageButton = null; + Button synMessageReturnButton = null; + Button asynMessageButton = null; + Button asynMessageReturnButton = null; + + Combo searchText = null; + Group kindSelection = null; + Button caseSensitive = null; + + Label result = null; + + Button parentOkButton = null; + + /** + * Creates the dialog contents + * + * @param parent the parent widget + * @param provider the provider which handle the action + * @param okButton of the dialog (to be enabled/disabled) + * @param expressionList list of strings already searched for + */ + /** + */ + public TabContents(Composite parent, ISDGraphNodeSupporter provider, Button okButton, String[] expressionList) { + super(parent, SWT.NONE); + setOkButton(okButton); + setLayout(new GridLayout()); + + graphNodeTypeListener = new GraphNodeTypeListener(); + expressionListener = new ExpressionListener(); + + // Inform the user how to fill the string to search + Label searchTitle = new Label(this, SWT.LEFT); + searchTitle.setText(SDMessages._26); + Composite searchPart = new Composite(this, SWT.NONE); + GridData searchPartData = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_FILL); + GridLayout searchPartLayout = new GridLayout(); + searchPartLayout.numColumns = 2; + searchPart.setLayout(searchPartLayout); + searchPart.setLayoutData(searchPartData); + + // Create the user string input area + searchText = new Combo(searchPart, SWT.DROP_DOWN); + GridData comboData = new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_VERTICAL | GridData.VERTICAL_ALIGN_FILL); + /* + * GridData tabLayoutData2 = new GridData(GridData.HORIZONTAL_ALIGN_FILL| GridData.VERTICAL_ALIGN_FILL); + */ + searchText.setLayoutData(comboData); + if (expressionList != null) { + for (int i = 0; i < expressionList.length; i++) { + searchText.add(expressionList[i]); + } + } + searchText.addModifyListener(expressionListener); + + // Create the case sensitive check button + caseSensitive = new Button(searchPart, SWT.CHECK); + caseSensitive.setText(SDMessages._27); + + // Create the group for searched graph node kind selection + kindSelection = new Group(this, SWT.SHADOW_NONE); + kindSelection.setText(SDMessages._25); + // kindSelection.setLayoutData(tabLayoutData2); + GridLayout kindSelectionLayout = new GridLayout(); + kindSelectionLayout.numColumns = 1; + kindSelection.setLayout(kindSelectionLayout); + GridData kindSelectionData = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL); + kindSelection.setLayoutData(kindSelectionData); + + // Create the lifeline check button + if (provider != null && provider.isNodeSupported(ISDGraphNodeSupporter.LIFELINE)) { + lifelineButton = new Button(kindSelection, SWT.CHECK); + String nodeName = provider.getNodeName(ISDGraphNodeSupporter.LIFELINE, null); + if (nodeName != null) + lifelineButton.setText(nodeName); + else + lifelineButton.setText(SDMessages._28); + lifelineButton.setEnabled(true); + lifelineButton.addSelectionListener(graphNodeTypeListener); + } + + if (provider != null && provider.isNodeSupported(ISDGraphNodeSupporter.STOP)) { + // Create the stop check button + stopButton = new Button(kindSelection, SWT.CHECK); + String nodeName = provider.getNodeName(ISDGraphNodeSupporter.STOP, null); + if (nodeName != null) + stopButton.setText(nodeName); + else + stopButton.setText(SDMessages._29); + stopButton.setEnabled(true); + stopButton.addSelectionListener(graphNodeTypeListener); + } + + if (provider != null && provider.isNodeSupported(ISDGraphNodeSupporter.SYNCMESSAGE)) { + // Create the synchronous message check button + synMessageButton = new Button(kindSelection, SWT.CHECK); + String nodeName = provider.getNodeName(ISDGraphNodeSupporter.SYNCMESSAGE, null); + if (nodeName != null) + synMessageButton.setText(nodeName); + else + synMessageButton.setText(SDMessages._30); + synMessageButton.setEnabled(true); + synMessageButton.addSelectionListener(graphNodeTypeListener); + } + + if (provider != null && provider.isNodeSupported(ISDGraphNodeSupporter.SYNCMESSAGERETURN)) { + // Create the synchronous message return check button + synMessageReturnButton = new Button(kindSelection, SWT.CHECK); + String nodeName = provider.getNodeName(ISDGraphNodeSupporter.SYNCMESSAGERETURN, null); + if (nodeName != null) + synMessageReturnButton.setText(nodeName); + else + synMessageReturnButton.setText(SDMessages._31); + synMessageReturnButton.setEnabled(true); + synMessageReturnButton.addSelectionListener(graphNodeTypeListener); + } + + if (provider != null && provider.isNodeSupported(ISDGraphNodeSupporter.ASYNCMESSAGE)) { + // Create the asynchronous message check button + asynMessageButton = new Button(kindSelection, SWT.CHECK); + String nodeName = provider.getNodeName(ISDGraphNodeSupporter.ASYNCMESSAGE, null); + if (nodeName != null) + asynMessageButton.setText(nodeName); + else + asynMessageButton.setText(SDMessages._32); + asynMessageButton.setEnabled(true); + asynMessageButton.addSelectionListener(graphNodeTypeListener); + } + + if (provider != null && provider.isNodeSupported(ISDGraphNodeSupporter.ASYNCMESSAGERETURN)) { + // Create the asynchronous message return check button + asynMessageReturnButton = new Button(kindSelection, SWT.CHECK); + String nodeName = provider.getNodeName(ISDGraphNodeSupporter.ASYNCMESSAGERETURN, null); + if (nodeName != null) + asynMessageReturnButton.setText(nodeName); + else + asynMessageReturnButton.setText(SDMessages._33); + asynMessageReturnButton.setEnabled(true); + asynMessageReturnButton.addSelectionListener(graphNodeTypeListener); + } + + result = new Label(this, SWT.LEFT); + result.setText(SDMessages._23); + result.setVisible(false); + } + + /** + * @param t + */ + public void setResult(boolean found) { + result.setVisible(!found); + } + + /** + * + */ + public void updateOkButton() { + if (parentOkButton == null) { + return; + } + boolean enabled = (searchText.getText() != null && !searchText.getText().equals("")) && //$NON-NLS-1$ + (getLifelineButtonSelection() || getStopButtonSelection() || getSynMessageButtonSelection() || getSynMessageReturnButtonSelection() || getAsynMessageButtonSelection() || getAsynMessageReturnButtonSelection()); + parentOkButton.setEnabled(enabled); + } + + /** + * @param okButton + * @todo Generated comment + */ + public void setOkButton(Button okButton) { + parentOkButton = okButton; + } + + /** + * Returns the asynchronous message check button state + * + * @return true if check, false otherwise + */ + public boolean getAsynMessageButtonSelection() { + if (asynMessageButton != null) + return asynMessageButton.getSelection(); + else + return false; + } + + /** + * Returns the asynchronous message return check button state + * + * @return true if check, false otherwise + */ + public boolean getAsynMessageReturnButtonSelection() { + if (asynMessageReturnButton != null) + return asynMessageReturnButton.getSelection(); + else + return false; + } + + /** + * Returns the case sensitive check button state + * + * @return true if check, false otherwise + */ + public boolean getCaseSensitiveSelection() { + if (caseSensitive != null) + return caseSensitive.getSelection(); + else + return false; + } + + /** + * Returns the lifeline check button state + * + * @return true if check, false otherwise + */ + public boolean getLifelineButtonSelection() { + if (lifelineButton != null) + return lifelineButton.getSelection(); + else + return false; + } + + /** + * Returns the user input string + * + * @return the string to search for + */ + public String getSearchText() { + return searchText.getText(); + } + + /** + * Returns the stop check button state + * + * @return true if check, false otherwise + */ + public boolean getStopButtonSelection() { + if (stopButton != null) + return stopButton.getSelection(); + else + return false; + } + + /** + * Returns the synchronous message check button state + * + * @return true if check, false otherwise + */ + public boolean getSynMessageButtonSelection() { + if (synMessageButton != null) + return synMessageButton.getSelection(); + else + return false; + } + + /** + * Returns the synchronous message return check button state + * + * @return true if check, false otherwise + */ + public boolean getSynMessageReturnButtonSelection() { + if (synMessageReturnButton != null) + return synMessageReturnButton.getSelection(); + else + return false; + } + + /** + * Set the asynchronous message check button state + */ + public void setAsynMessageButtonSelection(boolean state) { + if (asynMessageButton != null) + asynMessageButton.setSelection(state); + } + + /** + * Set the asynchronous message return check button state + */ + public void setAsynMessageReturnButtonSelection(boolean state) { + if (asynMessageReturnButton != null) + asynMessageReturnButton.setSelection(state); + } + + /** + * Set the case sensitive check button state + */ + public void setCaseSensitiveSelection(boolean state) { + if (caseSensitive != null) + caseSensitive.setSelection(state); + } + + /** + * Set the lifeline check button state + */ + public void setLifelineButtonSelection(boolean state) { + if (lifelineButton != null) + lifelineButton.setSelection(state); + } + + /** + * Set the user input string + */ + public void setSearchText(String text) { + searchText.setText(text); + } + + /** + * Set the stop check button state + */ + public void setStopButtonSelection(boolean state) { + if (stopButton != null) + stopButton.setSelection(state); + } + + /** + * Set the synchronous message check button state + */ + public void setSynMessageButtonSelection(boolean state) { + if (synMessageButton != null) + synMessageButton.setSelection(state); + } + + /** + * Set the synchronous message return check button state + */ + public void setSynMessageReturnButtonSelection(boolean state) { + if (synMessageReturnButton != null) + synMessageReturnButton.setSelection(state); + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/Messages.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/Messages.java new file mode 100644 index 0000000000..d184229560 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/Messages.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2011 Ericsson. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl.messages"; //$NON-NLS-1$ + public static String TmfUml2SDSyncLoader_ViewName; + public static String TmfUml2SDSyncLoader_CategoryLifeline; + public static String TmfUml2SDSyncLoader_CategoryMessage; + public static String TmfUml2SDSyncLoader_FrameName; + public static String TmfUml2SDSyncLoader_SearchJobDescrition; + public static String TmfUml2SDSyncLoader_SearchNotFound; + + public static String TmfUml2SDSyncCloader_EventTypeSend; + public static String TmfUml2SDSyncCloader_EventTypeReceive; + public static String TmfUml2SDSyncCloader_FieldSender; + public static String TmfUml2SDSyncCloader_FieldReceiver; + public static String TmfUml2SDSyncCloader_FieldSignal; + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/TmfAsyncMessage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/TmfAsyncMessage.java new file mode 100644 index 0000000000..6c21128c7b --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/TmfAsyncMessage.java @@ -0,0 +1,62 @@ +/********************************************************************** + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl; + +/** + * TmfAsyncMessage + *

+ * Extends AsyncMessage class to provide additional information about the trace event. + *

+ */ +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.AsyncMessage; +import org.eclipse.linuxtools.tmf.uml2sd.ITmfAsyncSequenceDiagramEvent; + +public class TmfAsyncMessage extends AsyncMessage implements ITmfAsyncSequenceDiagramEvent { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + protected ITmfAsyncSequenceDiagramEvent fSdEvent; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public TmfAsyncMessage(ITmfAsyncSequenceDiagramEvent sdEvent, int eventOccurrence) { + this.fSdEvent = sdEvent; + setEventOccurrence(eventOccurrence); + setName(sdEvent.getName()); + setStartTime(sdEvent.getStartTime()); + setEndTime(sdEvent.getEndTime()); + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.ust.examples.event.ISequenceDiagramEvent#getSender() + */ + @Override + public String getSender() { + return fSdEvent.getSender(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.ust.examples.event.ISequenceDiagramEvent#getReceiver() + */ + @Override + public String getReceiver() { + return fSdEvent.getReceiver(); + } +} \ No newline at end of file diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/TmfSyncMessage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/TmfSyncMessage.java new file mode 100644 index 0000000000..557aca1f53 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/TmfSyncMessage.java @@ -0,0 +1,61 @@ +/********************************************************************** + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl; + +/** + * TmfSyncMessages + *

+ * Extends AsyncMessage class to provide additional information about the trace event. + *

+ */ +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SyncMessage; +import org.eclipse.linuxtools.tmf.uml2sd.ITmfSyncSequenceDiagramEvent; + +public class TmfSyncMessage extends SyncMessage implements ITmfSyncSequenceDiagramEvent { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + protected ITmfSyncSequenceDiagramEvent fSdEvent; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public TmfSyncMessage(ITmfSyncSequenceDiagramEvent sdEvent, int eventOccurrence) { + this.fSdEvent = sdEvent; + setEventOccurrence(eventOccurrence); + setName(sdEvent.getName()); + setTime(sdEvent.getStartTime()); + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.ust.examples.event.ISequenceDiagramEvent#getSender() + */ + @Override + public String getSender() { + return fSdEvent.getSender(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.ust.examples.event.ISequenceDiagramEvent#getReceiver() + */ + @Override + public String getReceiver() { + return fSdEvent.getReceiver(); + } +} \ No newline at end of file diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/TmfUml2SDSyncLoader.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/TmfUml2SDSyncLoader.java new file mode 100644 index 0000000000..d383a5fcd4 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/TmfUml2SDSyncLoader.java @@ -0,0 +1,1334 @@ +/********************************************************************** + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.linuxtools.tmf.component.TmfComponent; +import org.eclipse.linuxtools.tmf.event.TmfEvent; +import org.eclipse.linuxtools.tmf.event.TmfEventField; +import org.eclipse.linuxtools.tmf.event.TmfNoSuchFieldException; +import org.eclipse.linuxtools.tmf.event.TmfTimeRange; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.experiment.TmfExperiment; +import org.eclipse.linuxtools.tmf.request.ITmfDataRequest; +import org.eclipse.linuxtools.tmf.request.ITmfEventRequest; +import org.eclipse.linuxtools.tmf.request.TmfDataRequest; +import org.eclipse.linuxtools.tmf.request.TmfEventRequest; +import org.eclipse.linuxtools.tmf.signal.TmfExperimentDisposedSignal; +import org.eclipse.linuxtools.tmf.signal.TmfExperimentSelectedSignal; +import org.eclipse.linuxtools.tmf.signal.TmfRangeSynchSignal; +import org.eclipse.linuxtools.tmf.signal.TmfSignal; +import org.eclipse.linuxtools.tmf.signal.TmfSignalHandler; +import org.eclipse.linuxtools.tmf.signal.TmfTimeSynchSignal; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Frame; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.Lifeline; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDAdvancedPagingProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDFilterProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDFindProvider; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDGraphNodeSupporter; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.Criteria; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.FilterCriteria; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.widgets.FilterListDialog; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.IUml2SDLoader; +import org.eclipse.linuxtools.tmf.uml2sd.ITmfSyncSequenceDiagramEvent; +import org.eclipse.linuxtools.tmf.uml2sd.TmfSyncSequenceDiagramEvent; +import org.eclipse.ui.ISelectionListener; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * TmfUml2SDSyncLoader + *

+ * Implementation of the org.eclipse.linuxtools.tmf.ui.Uml2SDLoader extension point to provide + * a Sequence Diagram loader for a UST trace with specific trace content for sending and + * receiving signals between components. Includes default implementation for the TmfEvent parsing. + *

+ */ +public class TmfUml2SDSyncLoader extends TmfComponent implements IUml2SDLoader, ISDFindProvider, ISDFilterProvider, ISDAdvancedPagingProvider, ISelectionListener { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + protected final static String TITLE = Messages.TmfUml2SDSyncLoader_ViewName; + protected final static int DEFAULT_BLOCK_SIZE = 50000; + protected final static int MAX_NUM_OF_MSG = 10000; + protected static final long INITIAL_WINDOW_OFFSET = (1L * 100 * 1000 * 1000); // .1sec + + // Experiment attributes + protected TmfExperiment fExperiment = null; + protected ITmfEventRequest fIndexRequest = null; + protected ITmfEventRequest fPageRequest = null; + volatile protected boolean fIsSignalSent = false; + volatile protected long fInitialWindow = INITIAL_WINDOW_OFFSET; + + // The view and event attributes + protected SDView fView = null; + protected Frame fFrame = null; + protected List fEvents = new ArrayList(); + + // Checkpoint and page attributes + protected List fCheckPoints = new ArrayList(MAX_NUM_OF_MSG); + volatile protected int fCurrentPage = 0; + protected TmfTimestamp fCurrentTime = null; + volatile protected boolean fIsSelect = false; + + // Search attributes + protected SearchJob fFindJob = null; + protected List fFindResults = new ArrayList(); + protected Criteria fFindCriteria = null; + volatile protected int fCurrentFindIndex = 0; + + // Filter attributes + protected List fFilterCriteria = null; + + // Thread synchronization + protected ReentrantLock fLock = new ReentrantLock(); + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + public TmfUml2SDSyncLoader() { + super(TITLE); + } + + public TmfUml2SDSyncLoader(String name) { + super(name); + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + + /** + * @return returns the current time if available else null + */ + public TmfTimestamp getCurrentTime() { + fLock.lock(); + try { + if (fCurrentTime != null) { + return fCurrentTime.clone(); + } + return null; + } finally { + fLock.unlock(); + } + } + + /** + * Waits for the page request to be completed + */ + public void waitForCompletion() { + fLock.lock(); + ITmfEventRequest request = fPageRequest; + fLock.unlock(); + if (request != null) + try { + request.waitForCompletion(); + } catch (InterruptedException e) { + // ignore + } + } + + /** + * Spawns a request to index the experiment (checkpoints creation) as well as it fills + * the first page. + * + * @param signal The experiment selected signal + */ + @SuppressWarnings("unchecked") + @TmfSignalHandler + public void experimentSelected(TmfExperimentSelectedSignal signal) { + + fLock.lock(); + try { + // Update the trace reference + TmfExperiment exp = (TmfExperiment) signal.getExperiment(); + if (!exp.equals(fExperiment)) { + fExperiment = exp; + } + + // TmfTimeRange window = getInitTRange(fExperiment.getTimeRange()); + TmfTimeRange window = TmfTimeRange.Eternity; + + fIndexRequest = new TmfEventRequest(TmfEvent.class, window, TmfDataRequest.ALL_DATA, DEFAULT_BLOCK_SIZE, ITmfDataRequest.ExecutionType.BACKGROUND) { + + private TmfTimestamp fFirstTime = null; + private TmfTimestamp fLastTime = null; + private int fNbSeqEvents = 0; + private List fSdEvents = new ArrayList(MAX_NUM_OF_MSG); + + @Override + public void handleData(TmfEvent event) { + super.handleData(event); + + ITmfSyncSequenceDiagramEvent sdEvent = getSequnceDiagramEvent(event); + + if (sdEvent != null) { + ++fNbSeqEvents; + + if (fFirstTime == null) { + fFirstTime = event.getTimestamp().clone(); + } + + fLastTime = event.getTimestamp().clone(); + + if ((fNbSeqEvents % MAX_NUM_OF_MSG) == 0) { + fLock.lock(); + try { + fCheckPoints.add(new TmfTimeRange(fFirstTime, fLastTime)); + if (fView != null) { + fView.updateCoolBar(); + } + } finally { + fLock.unlock(); + } + fFirstTime = null; + + } + + if (fNbSeqEvents > MAX_NUM_OF_MSG) { + // page is full + return; + } + + fSdEvents.add(sdEvent); + + if (fNbSeqEvents == MAX_NUM_OF_MSG) { + fillCurrentPage(fSdEvents); + } + } + } + + @Override + public void handleSuccess() { + // long indexingEnd = System.nanoTime(); + if ((fFirstTime != null) && (fLastTime != null)) { + + fLock.lock(); + try { + fCheckPoints.add(new TmfTimeRange(fFirstTime, fLastTime)); + if (fView != null) { + fView.updateCoolBar(); + } + } finally { + fLock.unlock(); + } + } + + if (fNbSeqEvents <= MAX_NUM_OF_MSG) { + fillCurrentPage(fSdEvents); + } + + super.handleSuccess(); + } + + @Override + public void handleCompleted() { + if (fEvents.size() == 0) { + fFrame = new Frame(); + fView.setFrameSync(fFrame); + } + super.handleCompleted(); + } + }; + + ((TmfExperiment) fExperiment).sendRequest((ITmfDataRequest) fIndexRequest); + } finally { + fLock.unlock(); + } + } + + /** + * @param signal The experiment disposed signal + */ + @TmfSignalHandler + public void experimentDisposed(TmfExperimentDisposedSignal signal) { + fLock.lock(); + try { + if ((fIndexRequest != null) && !fIndexRequest.isCompleted()) { + fIndexRequest.cancel(); + fIndexRequest = null; + } + + cancelOngoingRequests(); + + if (fFilterCriteria != null) { + fFilterCriteria.clear(); + } + + FilterListDialog.deactivateSavedGlobalFilters(); + + resetLoader(); + } finally { + fLock.unlock(); + } + } + + /** + * Moves to the page that contains the time provided by the signal. The messages will be selected + * if the provided time is the time of a message. + * + * @param signal The Time synch signal. + */ + @TmfSignalHandler + public void synchToTime(TmfTimeSynchSignal signal) { + fLock.lock(); + try { + if ((signal.getSource() != this) && (fFrame != null)) { + + fCurrentTime = signal.getCurrentTime(); + fIsSelect = true; + moveToMessage(); + } + } finally { + fLock.unlock(); + } + } + + /** + * Moves to the page that contains the current time provided by signal. + * No message will be selected however the focus will be set to the message + * if the provided time is the time of a message. + * + * @param signal The time range sync signal + */ + @TmfSignalHandler + public void synchToTimeRange(TmfRangeSynchSignal signal) { + fLock.lock(); + try { + if ((signal.getSource() != this) && (fFrame != null) && !fIsSignalSent) { + TmfTimeRange newTimeRange = signal.getCurrentRange(); + TmfTimestamp delta = newTimeRange.getEndTime().getDelta(newTimeRange.getStartTime()); + fInitialWindow = delta.getValue(); + + fIsSelect = false; + fCurrentTime = signal.getCurrentTime(); + + moveToMessage(); + } + } finally { + fLock.unlock(); + } + + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.IUml2SDLoader#setViewer(org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView) + */ + @SuppressWarnings("unchecked") + @Override + public void setViewer(SDView viewer) { + + fLock.lock(); + try { + fView = viewer; + PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService().addPostSelectionListener(this); + fView.setSDFindProvider(this); + fView.setSDPagingProvider(this); + fView.setSDFilterProvider(this); + + resetLoader(); + + fExperiment = (TmfExperiment) TmfExperiment.getCurrentExperiment(); + if (fExperiment != null) { + experimentSelected(new TmfExperimentSelectedSignal(this, fExperiment)); + } + } finally { + fLock.unlock(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.load.IUml2SDLoader#getTitleString() + */ + @Override + public String getTitleString() { + return getName(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.component.TmfComponent#dispose() + */ + @Override + public void dispose() { + super.dispose(); + fLock.lock(); + try { + PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService().removePostSelectionListener(this); + fView.setSDFindProvider(null); + fView.setSDPagingProvider(null); + fView.setSDFilterProvider(null); + fView = null; + } finally { + fLock.unlock(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.hyades.uml2sd.ui.actions.provider.ISDGraphNodeSupporter#isNodeSupported(int) + */ + @Override + public boolean isNodeSupported(int nodeType) { + switch (nodeType) { + case ISDGraphNodeSupporter.LIFELINE: + case ISDGraphNodeSupporter.SYNCMESSAGE: + return true; + + default: + break; + } + return false; + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDGraphNodeSupporter#getNodeName(int, java.lang.String) + */ + @Override + public String getNodeName(int nodeType, String loaderClassName) { + switch (nodeType) { + case ISDGraphNodeSupporter.LIFELINE: + return Messages.TmfUml2SDSyncLoader_CategoryLifeline; + case ISDGraphNodeSupporter.SYNCMESSAGE: + return Messages.TmfUml2SDSyncLoader_CategoryMessage; + } + return ""; //$NON-NLS-1$ + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.ISelection) + */ + @Override + public void selectionChanged(IWorkbenchPart part, ISelection selection) { + ISelection sel = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService().getSelection(); + if (sel != null && (sel instanceof StructuredSelection)) { + StructuredSelection stSel = (StructuredSelection) sel; + if (stSel.getFirstElement() instanceof TmfSyncMessage) { + TmfSyncMessage syncMsg = ((TmfSyncMessage) stSel.getFirstElement()); + broadcast(new TmfTimeSynchSignal(this, syncMsg.getStartTime())); + } + } + } + + /* + * (non-Javadoc) + * @see + * org.eclipse.hyades.uml2sd.ui.actions.provider.ISDFindProvider#find(org.eclipse.hyades.uml2sd.ui.actions.widgets.Criteria) + */ + @Override + public boolean find(Criteria toSearch) { + fLock.lock(); + try { + if (fFrame == null) { + return false; + } + + if (fFindResults == null || fFindCriteria == null || !fFindCriteria.compareTo(toSearch)) { + fFindResults = new CopyOnWriteArrayList(); + fFindCriteria = toSearch; + if (fFindCriteria.isLifeLineSelected()) { + for (int i = 0; i < fFrame.lifeLinesCount(); i++) { + if (fFindCriteria.matches(fFrame.getLifeline(i).getName())) { + fFindResults.add(fFrame.getLifeline(i)); + } + } + } + + ArrayList msgs = new ArrayList(); + if (fFindCriteria.isSyncMessageSelected()) { + for (int i = 0; i < fFrame.syncMessageCount(); i++) { + if (fFindCriteria.matches(fFrame.getSyncMessage(i).getName())) { + msgs.add(fFrame.getSyncMessage(i)); + } + } + } + + if (msgs.size() > 0) { + fFindResults.addAll(msgs); + } + + @SuppressWarnings("rawtypes") + List selection = fView.getSDWidget().getSelection(); + if (selection != null && selection.size() == 1) + fCurrentFindIndex = fFindResults.indexOf(selection.get(0)) + 1; + else + fCurrentFindIndex = 0; + } else { + fCurrentFindIndex++; + } + + if (fFindResults.size() > fCurrentFindIndex) { + GraphNode current = (GraphNode) fFindResults.get(fCurrentFindIndex); + fView.getSDWidget().moveTo(current); + return true; + } else { + fFindResults = null; + fCurrentFindIndex =0; + return findInNextPages(fFindCriteria); // search in other page + } + } finally { + fLock.unlock(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDFindProvider#cancel() + */ + @Override + public void cancel() { + cancelOngoingRequests(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDFilterProvider#filter(java.util.List) + */ + @SuppressWarnings("unchecked") + @Override + public boolean filter(List filters) { + fLock.lock(); + try { + cancelOngoingRequests(); + + List list = (List)filters; + fFilterCriteria = new ArrayList(list); + + fillCurrentPage(fEvents); + + } finally { + fLock.unlock(); + } + return true; + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDPagingProvider#hasNextPage() + */ + @Override + public boolean hasNextPage() { + fLock.lock(); + try { + int size = fCheckPoints.size(); + if (size > 0) { + return fCurrentPage < size - 1; + } + } finally { + fLock.unlock(); + } + return false; + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDPagingProvider#hasPrevPage() + */ + @Override + public boolean hasPrevPage() { + fLock.lock(); + try { + return fCurrentPage > 0; + } finally { + fLock.unlock(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDPagingProvider#nextPage() + */ + @Override + public void nextPage() { + fLock.lock(); + try { + // Safety check + if (fCurrentPage >= fCheckPoints.size() - 1) + return; + + cancelOngoingRequests(); + fCurrentTime = null; + fCurrentPage++; + moveToPage(); + } finally { + fLock.unlock(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDPagingProvider#prevPage() + */ + @Override + public void prevPage() { + fLock.lock(); + try { + // Safety check + if (fCurrentPage <= 0) + return; + + cancelOngoingRequests(); + fCurrentTime = null; + fCurrentPage--; + moveToPage(); + } finally { + fLock.unlock(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDPagingProvider#firstPage() + */ + @Override + public void firstPage() { + fLock.lock(); + try { + + cancelOngoingRequests(); + fCurrentTime = null; + fCurrentPage = 0; + moveToPage(); + } finally { + fLock.unlock(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDPagingProvider#lastPage() + */ + @Override + public void lastPage() { + fLock.lock(); + try { + cancelOngoingRequests(); + fCurrentTime = null; + fCurrentPage = fCheckPoints.size() - 1; + moveToPage(); + } finally { + fLock.unlock(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDAdvancedPagingProvider#currentPage() + */ + @Override + public int currentPage() { + fLock.lock(); + try { + return fCurrentPage; + } finally { + fLock.unlock(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDAdvancedPagingProvider#pagesCount() + */ + @Override + public int pagesCount() { + fLock.lock(); + try { + return fCheckPoints.size(); + } finally { + fLock.unlock(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.ui.views.uml2sd.handlers.provider.ISDAdvancedPagingProvider#pageNumberChanged(int) + */ + @Override + public void pageNumberChanged(int pageNumber) { + fLock.lock(); + try { + cancelOngoingRequests(); + + if (pageNumber < 0) { + pageNumber = 0; + } + int size = fCheckPoints.size(); + if (pageNumber > size - 1) { + pageNumber = size - 1; + } + fCurrentPage = pageNumber; + moveToPage(); + } finally { + fLock.unlock(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.component.TmfComponent#broadcast(org.eclipse.linuxtools.tmf.signal.TmfSignal) + */ + @Override + public void broadcast(TmfSignal signal) { + fIsSignalSent = true; + super.broadcast(signal); + fIsSignalSent = false; + } + + /** + * Cancels any ongoing find operation + */ + protected void cancelOngoingRequests() { + fLock.lock(); + try { + // Cancel the search thread + if (fFindJob != null) { + fFindJob.cancel(); + } + fFindResults = null; + fFindCriteria = null; + fCurrentFindIndex = 0; + + if ((fPageRequest != null) && !fPageRequest.isCompleted()) { + fPageRequest.cancel(); + fPageRequest = null; + } + } finally { + fLock.unlock(); + } + } + + /** + * Resets loader attributes + */ + protected void resetLoader() { + fLock.lock(); + try { + fCurrentTime = null; + fEvents.clear(); + fCheckPoints.clear(); + fCurrentPage = 0; + fCurrentFindIndex = 0; + fFindCriteria = null; + fFindResults = null; + fInitialWindow = INITIAL_WINDOW_OFFSET; + fView.setFrameSync(new Frame()); + fFrame = null; + } + finally { + fLock.unlock(); + } + + } + + /** + * Fills current page with sequence diagram content. + * @param events sequence diagram events + */ + protected void fillCurrentPage(List events) { + + fLock.lock(); + try { + fEvents = new ArrayList(events); + if (fView != null) { + fView.toggleWaitCursorAsync(true); + } + } finally { + fLock.unlock(); + } + + final Frame frame = new Frame(); + if (events.size() > 0) { + + HashMap nodeToLifelineMap = new HashMap(); + + frame.setName(Messages.TmfUml2SDSyncLoader_FrameName); + + for (int i = 0; i < events.size(); i++) { + + ITmfSyncSequenceDiagramEvent sdEvent = events.get(i); + + if ((nodeToLifelineMap.get(sdEvent.getSender()) == null) && (!filterLifeLine(sdEvent.getSender()))) { + Lifeline lifeline = new Lifeline(); + lifeline.setName(sdEvent.getSender()); + nodeToLifelineMap.put(sdEvent.getSender(), lifeline); + frame.addLifeLine(lifeline); + } + + if ((nodeToLifelineMap.get(sdEvent.getReceiver()) == null) && (!filterLifeLine(sdEvent.getReceiver()))) { + Lifeline lifeline = new Lifeline(); + lifeline.setName(sdEvent.getReceiver()); + nodeToLifelineMap.put(sdEvent.getReceiver(), lifeline); + frame.addLifeLine(lifeline); + } + } + + int eventOccurence = 1; + + for (int i = 0; i < events.size(); i++) { + ITmfSyncSequenceDiagramEvent sdEvent = (TmfSyncSequenceDiagramEvent) events.get(i); + + // Check message filter + if (filterMessage(sdEvent)) { + continue; + } + + // Set the message sender and receiver + Lifeline startLifeline = (Lifeline) nodeToLifelineMap.get(sdEvent.getSender()); + Lifeline endLifeline = (Lifeline) nodeToLifelineMap.get(sdEvent.getReceiver()); + + // Check if any of the lifelines were filtered + if (startLifeline == null || endLifeline == null) { + continue; + } + + int tmp = Math.max(startLifeline.getEventOccurrence(), endLifeline.getEventOccurrence()); + eventOccurence = Math.max(eventOccurence, tmp); + + startLifeline.setCurrentEventOccurrence(eventOccurence); + endLifeline.setCurrentEventOccurrence(eventOccurence); + + TmfSyncMessage message = new TmfSyncMessage(sdEvent, eventOccurence++); + + message.setStartLifeline(startLifeline); + message.setEndLifeline(endLifeline); + + message.setTime(sdEvent.getStartTime()); + + // add the message to the frame + frame.addMessage(message); + + } + fLock.lock(); + try { + if (!fView.getSDWidget().isDisposed()) { + fView.getSDWidget().getDisplay().asyncExec(new Runnable() { + + @Override + public void run() { + + fLock.lock(); + try { + // check if view was disposed in the meanwhile + if ((fView != null) && (!fView.getSDWidget().isDisposed())) { + fFrame = frame; + fView.setFrame(fFrame); + + if (fCurrentTime != null) { + moveToMessageInPage(); + } + + if (fFindCriteria != null) { + find(fFindCriteria); + } + + fView.toggleWaitCursorAsync(false); + } + } finally { + fLock.unlock(); + } + + } + }); + } + } + finally { + fLock.unlock(); + } + } + } + + /** + * Moves to a certain message defined by timestamp (across pages) + */ + protected void moveToMessage() { + int page = 0; + + fLock.lock(); + try { + page = getPage(fCurrentTime); + + if (page == fCurrentPage) { + moveToMessageInPage(); + return; + } + fCurrentPage = page; + moveToPage(false); + } finally { + fLock.unlock(); + } + } + + /** + * Moves to a certain message defined by timestamp in current page + */ + protected void moveToMessageInPage() { + fLock.lock(); + try { + TmfSyncMessage prevMessage = null; + TmfSyncMessage syncMessage = null; + boolean isExactTime = false; + for (int i = 0; i < fFrame.syncMessageCount(); i++) { + if (fFrame.getSyncMessage(i) instanceof TmfSyncMessage) { + syncMessage = (TmfSyncMessage) fFrame.getSyncMessage(i); + if (syncMessage.getStartTime().compareTo(fCurrentTime, false) == 0) { + isExactTime = true; + break; + } + else if (syncMessage.getStartTime().compareTo(fCurrentTime, false) > 0) { + if (prevMessage != null) { + syncMessage = prevMessage; + break; + } + } + prevMessage = syncMessage; + } + } + if (fIsSelect && isExactTime) { + fView.getSDWidget().moveTo(syncMessage); + } + else { + fView.getSDWidget().ensureVisible(syncMessage); + fView.getSDWidget().clearSelection(); + fView.getSDWidget().redraw(); + } + } + finally { + fLock.unlock(); + } + } + + /** + * Moves to a certain message defined by timestamp (across pages) + */ + protected void moveToPage() { + moveToPage(true); + } + + /** + * Moves to a certain page. + * @param notifyAll true to broadcast time range signal to other signal handlers else false + */ + protected void moveToPage(boolean notifyAll) { + + TmfTimeRange window = TmfTimeRange.Eternity; + + fLock.lock(); + try { + // Safety check + if (fCurrentPage > fCheckPoints.size()) { + return; + } + window = fCheckPoints.get(fCurrentPage); + } finally { + fLock.unlock(); + } + + fPageRequest = new TmfEventRequest(TmfEvent.class, window, TmfDataRequest.ALL_DATA, 1, ITmfDataRequest.ExecutionType.FOREGROUND) { + private List fSdEvent = new ArrayList(); + + @Override + public void handleData(TmfEvent event) { + super.handleData(event); + + ITmfSyncSequenceDiagramEvent sdEvent = getSequnceDiagramEvent(event); + + if (sdEvent != null) { + fSdEvent.add(sdEvent); + } + } + + @Override + public void handleSuccess() { + fillCurrentPage(fSdEvent); + super.handleSuccess(); + } + + }; + + ((TmfExperiment) fExperiment).sendRequest((ITmfDataRequest) fPageRequest); + + if (notifyAll) { + TmfTimeRange timeRange = getSignalTimeRange(window.getStartTime()); + broadcast(new TmfRangeSynchSignal(this, timeRange, timeRange.getStartTime())); + } + } + + /** + * Gets page that contains timestamp + * @param time The timestamp + * @return page that contains the time + */ + protected int getPage(TmfTimestamp time) { + int page; + int size; + fLock.lock(); + try { + size = fCheckPoints.size(); + for (page = 0; page < size; page++) { + TmfTimeRange timeRange = fCheckPoints.get(page); + if (timeRange.getEndTime().compareTo(time, false) >= 0) { + break; + } + } + if (page >= size) { + page = size - 1; + } + return page; + } finally { + fLock.unlock(); + } + } + + /** + * Background search in trace for expression in criteria. + * @param findCriteria The find criteria + * @return true if background request was started else false + */ + protected boolean findInNextPages(Criteria findCriteria) { + fLock.lock(); + try { + if (fFindJob != null) { + return true; + } + + int nextPage = fCurrentPage + 1; + + if ((nextPage) >= fCheckPoints.size()) { + // we are at the end + return false; + } + + TmfTimeRange window = new TmfTimeRange(fCheckPoints.get(nextPage).getStartTime().clone(), fCheckPoints.get(fCheckPoints.size()-1).getEndTime().clone()); + fFindJob = new SearchJob(findCriteria, window); + fFindJob.schedule(); + fView.toggleWaitCursorAsync(true); + } finally { + fLock.unlock(); + } + return true; + } + + /** + * Gets time range for time range signal. + * @param startTime The start time of time range. + * @return + */ + protected TmfTimeRange getSignalTimeRange(TmfTimestamp startTime) { + fLock.lock(); + try { + TmfTimestamp initialEndOfWindow = new TmfTimestamp(startTime.getValue() + fInitialWindow, startTime.getScale(), startTime.getPrecision()); + return new TmfTimeRange(startTime, initialEndOfWindow); + } + finally { + fLock.unlock(); + } + } + + /** + * Checks if filter criteria matches the message name in given SD event. + * @param sdEvent The SD event to check + * @return true if match else false. + */ + protected boolean filterMessage(ITmfSyncSequenceDiagramEvent sdEvent) { + fLock.lock(); + try { + if (fFilterCriteria != null) { + for(FilterCriteria criteria : fFilterCriteria) { + if (criteria.isActive() && criteria.getCriteria().isSyncMessageSelected() ) { + + if(criteria.getCriteria().matches(sdEvent.getName())) { + return true; + } + } + } + } + } finally { + fLock.unlock(); + } + return false; + } + + /** + * Checks if filter criteria matches a lifeline name (sender or receiver) in given SD event. + * @param sdEvent The SD event to check + * @return true if match else false. + */ + protected boolean filterLifeLine(String lifeline) { + fLock.lock(); + try { + if (fFilterCriteria != null) { + for(FilterCriteria criteria : fFilterCriteria) { + if (criteria.isActive() && criteria.getCriteria().isLifeLineSelected()) { + + if(criteria.getCriteria().matches(lifeline)) { + return true; + } + } + } + } + } finally { + fLock.unlock(); + } + return false; + } + + /** + * Job to search in trace for given time range. + */ + protected class SearchJob extends Job { + + final protected SearchEventRequest fSearchRequest; + + /** + * Constructor + * + * @param findCriteria The search criteria + * @param window Time range to search in + */ + public SearchJob(Criteria findCriteria, TmfTimeRange window) { + super(Messages.TmfUml2SDSyncLoader_SearchJobDescrition); + fSearchRequest = new SearchEventRequest(window, TmfDataRequest.ALL_DATA, 1, ITmfDataRequest.ExecutionType.FOREGROUND, findCriteria); + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + protected IStatus run(IProgressMonitor monitor) { + fSearchRequest.setMonitor(monitor); + + ((TmfExperiment) fExperiment).sendRequest((ITmfDataRequest) fSearchRequest); + + try { + fSearchRequest.waitForCompletion(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + IStatus status = Status.OK_STATUS; + if (fSearchRequest.isFound() && fSearchRequest.getFoundTime() != null) { + fCurrentTime = fSearchRequest.getFoundTime(); + + // Avoid double-selection. Selection will be done when calling find(criteria) + // after moving to relevant page + fIsSelect = false; + if (!fView.getSDWidget().isDisposed()) { + fView.getSDWidget().getDisplay().asyncExec(new Runnable() { + + @Override + public void run() { + moveToMessage(); + } + }); + } + } + else { + if (monitor.isCanceled()) { + status = Status.CANCEL_STATUS; + } + else { + // String was not found + status = new Status(Status.WARNING, TmfUiPlugin.PLUGIN_ID, Messages.TmfUml2SDSyncLoader_SearchNotFound); + } + setProperty(IProgressConstants.KEEP_PROPERTY, Boolean.TRUE); + } + monitor.done(); + + fLock.lock(); + try { + fView.toggleWaitCursorAsync(false); + fFindJob = null; + } finally { + fLock.unlock(); + } + + return status; + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.jobs.Job#canceling() + */ + @Override + protected void canceling() { + fSearchRequest.cancel(); + fLock.lock(); + try { + fFindJob = null; + } finally { + fLock.unlock(); + } + } + } + + /** + * TMF event request for searching within trace. + */ + protected class SearchEventRequest extends TmfEventRequest { + + final private Criteria fCriteria; + + private IProgressMonitor fMonitor; + private boolean fIsFound = false; + private TmfTimestamp fFoundTime = null; + + /** + * Constructor + * @param range @see org.eclipse.linuxtools.tmf.request.TmfEventRequest#TmfEventRequest(...) + * @param nbRequested @see org.eclipse.linuxtools.tmf.request.TmfEventRequest#handleData(...) + * @param blockSize @see org.eclipse.linuxtools.tmf.request.TmfEventRequest#handleData(...) + * @param execType @see org.eclipse.linuxtools.tmf.request.TmfEventRequest#handleData(...) + * @param criteria The search criteria + */ + public SearchEventRequest(TmfTimeRange range, int nbRequested, int blockSize, ExecutionType execType, Criteria criteria) { + this(range, nbRequested, blockSize, execType, criteria, null); + } + + /** + * Constructor + * @param range @see org.eclipse.linuxtools.tmf.request.TmfEventRequest#TmfEventRequest(...) + * @param nbRequested @see org.eclipse.linuxtools.tmf.request.TmfEventRequest#TmfEventRequest(...) + * @param blockSize @see org.eclipse.linuxtools.tmf.request.TmfEventRequest#TmfEventRequest(...) + * @param execType @see org.eclipse.linuxtools.tmf.request.TmfEventRequest#TmfEventRequest(...) + * @param criteria The search criteria + * @param monitor progress monitor + */ + public SearchEventRequest(TmfTimeRange range, int nbRequested, int blockSize, ExecutionType execType, Criteria criteria, IProgressMonitor monitor) { + super(TmfEvent.class, range, nbRequested, blockSize, execType); + fCriteria = new Criteria(criteria); + fMonitor = monitor; + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.request.TmfDataRequest#handleData(org.eclipse.linuxtools.tmf.event.TmfData) + */ + @Override + public void handleData(TmfEvent event) { + super.handleData(event); + + if ((fMonitor!= null) && fMonitor.isCanceled()) { + cancel(); + return; + } + + ITmfSyncSequenceDiagramEvent sdEvent = getSequnceDiagramEvent(event); + + if (sdEvent != null) { + + if (fCriteria.isLifeLineSelected()) { + if (fCriteria.matches(sdEvent.getSender())) { + fFoundTime = event.getTimestamp().clone(); + fIsFound = true; + super.cancel(); + } + + if (fCriteria.matches(sdEvent.getReceiver())) { + fFoundTime = event.getTimestamp().clone(); + fIsFound = true; + super.cancel(); + } + } + + if (fCriteria.isSyncMessageSelected()) { + if (fCriteria.matches(sdEvent.getName())) { + fFoundTime = event.getTimestamp().clone(); + fIsFound = true; + super.cancel(); + } + } + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.request.TmfDataRequest#handleCompleted() + */ + @Override + public void handleCompleted() { + super.handleCompleted(); + } + + /** + * Set progress monitor. + * @param monitor + */ + public void setMonitor(IProgressMonitor monitor) { + fMonitor = monitor; + } + + /** + * Check if find criteria was met. + * @return true if find criteria was met. + */ + public boolean isFound() { + return fIsFound; + } + + /** + * @return timestamp of found time. + */ + public TmfTimestamp getFoundTime() { + return fFoundTime; + } + } + + /** + * @param tmfEvent Event to parse for sequence diagram event details + * @return sequence diagram event if details are available else null + */ + protected ITmfSyncSequenceDiagramEvent getSequnceDiagramEvent(TmfEvent tmfEvent){ + //type = .*RECEIVE.* or .*SEND.* + //content = sender::receiver:,signal: + String eventType = tmfEvent.getType().toString(); + if (eventType.contains(Messages.TmfUml2SDSyncCloader_EventTypeSend) || + eventType.contains(Messages.TmfUml2SDSyncCloader_EventTypeReceive)) { + try { + Object sender = tmfEvent.getContent().getField(Messages.TmfUml2SDSyncCloader_FieldSender); + Object receiver = tmfEvent.getContent().getField(Messages.TmfUml2SDSyncCloader_FieldReceiver); + Object name = tmfEvent.getContent().getField(Messages.TmfUml2SDSyncCloader_FieldSignal); + if ((sender instanceof TmfEventField) && (receiver instanceof TmfEventField) && (name instanceof TmfEventField)) { + ITmfSyncSequenceDiagramEvent sdEvent = new TmfSyncSequenceDiagramEvent(tmfEvent, + ((TmfEventField)sender).getValue().toString(), + ((TmfEventField)receiver).getValue().toString(), + ((TmfEventField)name).getValue().toString()); + + return sdEvent; + } + } catch (TmfNoSuchFieldException e) { + } + } + return null; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/messages.properties b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/messages.properties new file mode 100644 index 0000000000..a7b1167812 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/impl/messages.properties @@ -0,0 +1,11 @@ +TmfUml2SDSyncLoader_ViewName=Component Interactions +TmfUml2SDSyncLoader_CategoryLifeline=Lifeline +TmfUml2SDSyncLoader_CategoryMessage=Interaction +TmfUml2SDSyncLoader_FrameName=Sequence Diagram +TmfUml2SDSyncLoader_SearchJobDescrition=Searching in sequence diagram ... +TmfUml2SDSyncCloader_EventTypeSend=SEND +TmfUml2SDSyncCloader_EventTypeReceive=RECEIVE +TmfUml2SDSyncCloader_FieldSender=sender +TmfUml2SDSyncCloader_FieldReceiver=receiver +TmfUml2SDSyncCloader_FieldSignal=signal +TmfUml2SDSyncLoader_SearchNotFound=String not found! diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/load/IUml2SDLoader.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/load/IUml2SDLoader.java new file mode 100755 index 0000000000..a3e5928631 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/load/IUml2SDLoader.java @@ -0,0 +1,41 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: IUml2SDLoader.java,v 1.2 2006/09/20 20:56:28 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.load; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; + +/** + * The interface all UML2SD loaders must implement + */ +public interface IUml2SDLoader { + + /** + * Set the viewer object to the loader that has been reloaded at the beginning + * of a new workbench session + */ + public void setViewer(SDView viewer); + + /** + * Title string for the UML2SD View when this loader is the one + * + * @return the string convenient for this loader + */ + public String getTitleString(); + + /** + * When another loader becomes the one the previous one is replaced It's time clean-up + * if needed (listeners to be removed for example) + */ + public void dispose(); + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/load/LoadersManager.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/load/LoadersManager.java new file mode 100755 index 0000000000..739359118f --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/load/LoadersManager.java @@ -0,0 +1,358 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: LoadersManager.java,v 1.5 2008/01/24 02:29:16 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.load; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtension; +import org.eclipse.core.runtime.IExtensionPoint; +import org.eclipse.core.runtime.Platform; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.linuxtools.tmf.Tracer; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDView; +import org.eclipse.ui.IViewReference; +import org.eclipse.ui.IWorkbenchPage; + +/** + * Manager class for the UML2SD extension point. + */ +public class LoadersManager { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + + public static final String LOADER_TAG = "uml2SDLoader"; //$NON-NLS-1$ + public static final String LOADER_PREFIX = LOADER_TAG + "."; //$NON-NLS-1$ + + // The instance + protected static LoadersManager loadersManager; + + // Maps for caching information + protected HashMap fViewLoaderMap = new HashMap(); + protected HashMap> fViewLoadersList = new HashMap>(); + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + /** + * This should not be used by the clients + */ + private LoadersManager() { + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /** + * A static method to get the manager instance. + * + * @return the manager instance + */ + public static LoadersManager getInstance() { + if (loadersManager == null) { + loadersManager = new LoadersManager(); + } + return loadersManager; + } + + /** + * Creates a loader instance and associate it to the view. It requires + * that the loader-view-association was created by an eclipse extension. + * + * @param className The name of the class to create an instance from + * @param view The UML2 Sequence Diagram view instance + * @return The created loader + */ + public IUml2SDLoader createLoader(String className, SDView view) { + + // Safety check + if (view == null) { + return null; + } + + String viewId = view.getViewSite().getId(); + + // Get loaders from all extensions for given view + List loaderElements = getLoaderConfigurationElements(viewId); + IConfigurationElement ce = getLoaderConfigurationElement(className, loaderElements); + + if (ce != null) { + // Assign a loader instance to this view + createLoaderForView(viewId, ce); + IUml2SDLoader loader = fViewLoaderMap.get(viewId); + if (loader != null) { + loader.setViewer(view); + return loader; + } + } + return null; + } + + /** + * Sets the loader to null for this view, a kind of clean-up while disposing. + * + * @param viewId the id of the view + */ + public void resetLoader(String viewId) { + IUml2SDLoader loader = (IUml2SDLoader) fViewLoaderMap.get(viewId); + if (loader != null) { + loader.dispose(); + } + fViewLoaderMap.put(viewId, null); + } + + /** + * Returns the loader in use in given Sequence Diagram View + * + * @param viewId The Sequence Diagram viewId. + * @return the current loader if any - null otherwise + */ + public IUml2SDLoader getCurrentLoader(String viewId) { + return getCurrentLoader(viewId, null); + } + + /** + * Returns the loader in use in this Sequence Diagram View + * + * @param viewId The Sequence Diagram viewId + * @param view The Sequence Diagram view (if known). Use null to reference the primary SD View. + * @return the current loader if any - null otherwise + */ + public IUml2SDLoader getCurrentLoader(String viewId, SDView view) { + if (viewId == null) { + return null; + } + + IWorkbenchPage persp = TmfUiPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getActivePage(); + + SDView sdView = view; + + try { + // Search the view corresponding to the viewId + if (sdView == null) { + IViewReference viewref = (IViewReference) persp.findViewReference(viewId); + if (viewref != null) { + sdView = (SDView) viewref.getView(false); + } + + if (sdView == null) { + // no corresponding view exists -> return null for the loader + return null; + } + } + + // Return the loader corresponding to that view (if any) + IUml2SDLoader loader = fViewLoaderMap.get(viewId); + if (loader == null) { + createLastLoaderIfAny(viewId); + loader = fViewLoaderMap.get(viewId); + } + + return loader; + } catch (Exception e) { + if (Tracer.isErrorTraced()) { + Tracer.traceError("Exception during getCurrentLoder(): " + e); //$NON-NLS-1$ + } + } + return null; + } + + /** + * Returns the loader class name that have been saved last time. + * + * @param viewId The view this loader belongs to + * @return the class name of the saved loader + */ + public String getSavedLoader(String viewId) { + IPreferenceStore p = TmfUiPlugin.getDefault().getPreferenceStore(); + return p.getString(LOADER_PREFIX + viewId); + } + + /** + * Saves the last loader in order to reload it on next session. + */ + public void saveLastLoader(String id, String id2) { + IPreferenceStore p = TmfUiPlugin.getDefault().getPreferenceStore(); + p.setValue(LOADER_PREFIX + id2, id); + } + + /** + * Changes the current unique loader to the given secondary viewId. + * + * @param loader The current loader + * @param id the view secondary id or null + */ + private void setCurrentLoader(IUml2SDLoader loader, String id) { + if (id == null) { + return; + } + + // Get the loader in use + IUml2SDLoader currentLoader = fViewLoaderMap.get(id); + + if ((currentLoader != null) && (currentLoader != loader)) { + if (loader != null) { + IWorkbenchPage persp = TmfUiPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getActivePage(); + try { + // Search view corresponding to the viewId + SDView sdview = null; + IViewReference viewref = (IViewReference) persp.findViewReference(id); + if (viewref != null) { + sdview = (SDView) viewref.getView(false); + } + + // Make everything clean for the new loader + if (sdview != null) { + sdview.resetProviders(); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + // The old loader is going to be kicked + currentLoader.dispose(); + } + + // Replace the current loader by the new one in the map + fViewLoaderMap.put(id, loader); + + // Store this loader in the preferences to be able to restore it when the workbench will be re-launched + if (loader != null) { + saveLastLoader(loader.getClass().getName(), id); + } + } + + /** + * Creates the last loader and saves it. If not last is not available, it creates + * and saves the default loader, else no loader is created. + * + * @param viewId The view ID. + */ + private void createLastLoaderIfAny(String viewId) { + // Get saved loader from preferences + String loaderName = getSavedLoader(viewId); + + // Get loaders from all extensions for given view + List loaderElements = getLoaderConfigurationElements(viewId); + IConfigurationElement ce = getLoaderConfigurationElement(loaderName, loaderElements); + + if (ce == null) { + ce = getDefaultLoader(loaderElements); + } + + if (ce != null) { + createLoaderForView(viewId, ce); + } + } + + /** + * Gets a list of loader configuration elements from the extension point registry for a given view. + * @param viewId The view ID + * @return List of extension point configuration elements. + */ + private List getLoaderConfigurationElements(String viewId) { + List list = (List) fViewLoadersList.get(viewId); + if (list != null) { + return list; + } + ArrayList ret = new ArrayList(); + IExtensionPoint iep = Platform.getExtensionRegistry().getExtensionPoint(TmfUiPlugin.PLUGIN_ID, LOADER_TAG); + if (iep == null) { + return ret; + } + + IExtension[] ie = iep.getExtensions(); + if (ie == null) { + return ret; + } + + for (int i = 0; i < ie.length; i++) { + IConfigurationElement c[] = ie[i].getConfigurationElements(); + for (int j = 0; j < c.length; j++) { + if (viewId.equals(c[j].getAttribute("view"))) { //$NON-NLS-1$ + ret.add(c[j]); + } + } + } + fViewLoadersList.put(viewId, ret); + return ret; + } + + /** + * Returns the loader configuration element for given loader class name and for the given + * list of configuration elements, if available else null. + * + * @param loaderClassName The loader class name. + * @param loaderElements The list of loader configuration elements + * @return Extension point configuration element + */ + private IConfigurationElement getLoaderConfigurationElement(String loaderClassName, List loaderElements) { + if (loaderClassName != null && loaderClassName.length() > 0) { + // Find configuration element corresponding to the saved loader + for (Iterator i = loaderElements.iterator(); i.hasNext();) { + IConfigurationElement ce = (IConfigurationElement) i.next(); + if (ce.getAttribute("class").equals(loaderClassName)) { //$NON-NLS-1$ + return ce; + } + } + } + return null; + } + + /** + * Returns the loader configuration element for the given list of configuration elements, if available else null. + * Note that if multiple default loaders are defined it selects the first one + + * @param loaderElements The list of loader configuration elements + * @return The default extension point configuration element. + */ + private IConfigurationElement getDefaultLoader(List loaderElements) { + // Look for a default loader + for (Iterator i = loaderElements.iterator(); i.hasNext();) { + IConfigurationElement ce = (IConfigurationElement) i.next(); + if (Boolean.valueOf(ce.getAttribute("default")).booleanValue()) { //$NON-NLS-1$ + return ce; + } + } + return null; + } + + /** + * Creates an instance of the loader class for a given extension point configuration element and + * also sets it as current loader for the given view. + * @param viewId The view ID. + * @param ce The extension point configuration element + */ + private void createLoaderForView(String viewId, IConfigurationElement ce) { + try { + Object obj = ce.createExecutableExtension("class"); //$NON-NLS-1$ + IUml2SDLoader l = (IUml2SDLoader) obj; + if (viewId != null) { + setCurrentLoader(l, viewId); + } + } catch (CoreException e4) { + System.err.println("Error 'uml2SDLoader' Extension point :" + e4); //$NON-NLS-1$ + } catch (Exception e5) { + e5.printStackTrace(); + System.err.println("Error 'uml2SDLoader' Extension point :" + e5); //$NON-NLS-1$ + } + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/preferences/SDViewPref.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/preferences/SDViewPref.java new file mode 100755 index 0000000000..37f27e9dc2 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/preferences/SDViewPref.java @@ -0,0 +1,383 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: SDViewPref.java,v 1.3 2008/01/24 02:28:51 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.preferences; + +import java.util.Hashtable; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceConverter; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.linuxtools.tmf.ui.TmfUiPlugin; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IColor; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.IFont; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.ISDPreferences; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.impl.ColorImpl; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.drawings.impl.FontImpl; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.widgets.Display; + +/** + * This is the Sequence Diagram preference handler This class is responsible for accessing the current user preferences + * selection This class also provider getters for each modifiable preferences + * + * @author sveyrier + */ +public class SDViewPref implements ISDPreferences, IPropertyChangeListener { + + public static final String BACK_COLOR_POSTFIX = "_BACK_COLOR";//$NON-NLS-1$ + public static final String FORE_COLOR_POSTFIX = "_FORE_COLOR";//$NON-NLS-1$ + public static final String TEXT_COLOR_POSTFIX = "_TEXT_COLOR";//$NON-NLS-1$ + + public static final String[] fontList = { PREF_LIFELINE, PREF_EXEC, PREF_SYNC_MESS, PREF_SYNC_MESS_RET, PREF_ASYNC_MESS, PREF_ASYNC_MESS_RET, PREF_FRAME, PREF_LIFELINE_HEADER, PREF_FRAME_NAME }; + + public static final String[] fontList2 = { SDMessages._88, SDMessages._89, SDMessages._90, SDMessages._91, SDMessages._92, SDMessages._93, SDMessages._94, SDMessages._95, SDMessages._96 }; + + public static final String[] prefBackColorList = { PREF_LIFELINE, PREF_EXEC, PREF_FRAME, PREF_LIFELINE_HEADER, PREF_FRAME_NAME }; + + public static final String[] prefForeColorList = { PREF_LIFELINE, PREF_EXEC, PREF_SYNC_MESS, PREF_SYNC_MESS_RET, PREF_ASYNC_MESS, PREF_ASYNC_MESS_RET, PREF_FRAME, PREF_LIFELINE_HEADER, PREF_FRAME_NAME }; + + public static final String[] prefTextColorList = { PREF_LIFELINE, PREF_SYNC_MESS, PREF_SYNC_MESS_RET, PREF_ASYNC_MESS, PREF_ASYNC_MESS_RET, PREF_LIFELINE_HEADER, PREF_FRAME_NAME }; + + protected Hashtable fontPref; + protected Hashtable foreColorPref; + protected Hashtable backColorPref; + protected Hashtable textColorPref; + + protected static SDViewPref handle = null; + protected IPreferenceStore prefStore = null; + protected IColor timeCompressionSelectionColor = null; + + protected boolean noFocusSelection = false; + + protected static final String TEMP_TAG = "_TEMP";//$NON-NLS-1$ + + /** + * Builds the Sequence Diagram preference handler: - Define the preference default values. - Load the currently used + * preferences setting + * + */ + protected SDViewPref() { + prefStore = TmfUiPlugin.getDefault().getPreferenceStore(); + + prefStore.setDefault(PREF_LINK_FONT, true); + prefStore.setDefault(PREF_EXCLUDE_EXTERNAL_TIME, true); + prefStore.setDefault(PREF_LIFELINE_WIDTH, 200); + prefStore.setDefault(PREF_USE_GRADIENT, true); + prefStore.setDefault(PREF_TOOLTIP, true); + + fontPref = new Hashtable(); + foreColorPref = new Hashtable(); + backColorPref = new Hashtable(); + textColorPref = new Hashtable(); + + for (int i = 0; i < fontList.length; i++) { + if (fontList[i].equals(PREF_FRAME_NAME)) { + FontData[] data = Display.getDefault().getSystemFont().getFontData(); + data[0].setStyle(SWT.BOLD); + PreferenceConverter.setDefault(prefStore, fontList[i], data[0]); + PreferenceConverter.setDefault(prefStore, fontList[i] + TEMP_TAG, data[0]); + } else { + PreferenceConverter.setDefault(prefStore, fontList[i], Display.getDefault().getSystemFont().getFontData()); + PreferenceConverter.setDefault(prefStore, fontList[i] + TEMP_TAG, Display.getDefault().getSystemFont().getFontData()); + } + } + + for (int i = 0; i < prefBackColorList.length; i++) { + IColor color; + if ((prefBackColorList[i].equals(PREF_EXEC)) || prefBackColorList[i].equals(PREF_FRAME_NAME)) + color = new ColorImpl(Display.getDefault(), 201, 222, 233); + else if (prefBackColorList[i].equals(PREF_LIFELINE)) + color = new ColorImpl(Display.getDefault(), 220, 220, 220); + else if (prefBackColorList[i].equals(PREF_LIFELINE_HEADER)) + color = new ColorImpl(Display.getDefault(), 245, 244, 244); + else + color = new ColorImpl(Display.getDefault(), 255, 255, 255); + PreferenceConverter.setDefault(prefStore, prefBackColorList[i] + BACK_COLOR_POSTFIX, ((Color) color.getColor()).getRGB()); + PreferenceConverter.setDefault(prefStore, prefBackColorList[i] + BACK_COLOR_POSTFIX + TEMP_TAG, ((Color) color.getColor()).getRGB()); + color.dispose(); + } + + for (int i = 0; i < prefForeColorList.length; i++) { + IColor color; + if (prefForeColorList[i].equals(PREF_LIFELINE)) + color = new ColorImpl(Display.getDefault(), 129, 129, 129); + else if (prefForeColorList[i].equals(PREF_FRAME_NAME)) + color = new ColorImpl(Display.getDefault(), 81, 153, 200); + else if (prefForeColorList[i].equals(PREF_LIFELINE_HEADER)) + color = new ColorImpl(Display.getDefault(), 129, 127, 137); + else + color = new ColorImpl(Display.getDefault(), 134, 176, 212); + PreferenceConverter.setDefault(prefStore, prefForeColorList[i] + FORE_COLOR_POSTFIX, ((Color) color.getColor()).getRGB()); + PreferenceConverter.setDefault(prefStore, prefForeColorList[i] + FORE_COLOR_POSTFIX + TEMP_TAG, ((Color) color.getColor()).getRGB()); + color.dispose(); + } + + for (int i = 0; i < prefTextColorList.length; i++) { + IColor color; + if (prefTextColorList[i].equals(PREF_LIFELINE)) + color = new ColorImpl(Display.getDefault(), 129, 129, 129); + else if (prefTextColorList[i].equals(PREF_FRAME_NAME)) + color = new ColorImpl(Display.getDefault(), 0, 0, 0); + else if (prefTextColorList[i].equals(PREF_LIFELINE_HEADER)) + color = new ColorImpl(Display.getDefault(), 129, 127, 137); + else + color = new ColorImpl(Display.getDefault(), 134, 176, 212); + PreferenceConverter.setDefault(prefStore, prefTextColorList[i] + TEXT_COLOR_POSTFIX, ((Color) color.getColor()).getRGB()); + PreferenceConverter.setDefault(prefStore, prefTextColorList[i] + TEXT_COLOR_POSTFIX + TEMP_TAG, ((Color) color.getColor()).getRGB()); + color.dispose(); + } + + IColor color = new ColorImpl(Display.getDefault(), 218, 232, 238); + PreferenceConverter.setDefault(prefStore, PREF_TIME_COMP, ((Color) color.getColor()).getRGB()); + color.dispose(); + + buildFontsAndColors(); + + prefStore.addPropertyChangeListener(this); + } + + /** + * Returns the PreferenceStore + * + * @return the PreferenceStore + */ + public IPreferenceStore getPreferenceStore() { + return prefStore; + } + + /** + * Apply the preferences in the preferences handler + */ + public void apply() { + buildFontsAndColors(); + prefStore.firePropertyChangeEvent("PREFOK", null, null); //$NON-NLS-1$ + } + + /** + * Returns an unique instance of the Sequence Diagram preference handler + * + * @return the preference handler instance + */ + static public SDViewPref getInstance() { + if (handle == null) + handle = new SDViewPref(); + return handle; + } + + /** + * Returns the chosen foreground color + * + * @return the foreground color + */ + @Override + public IColor getForeGroundColor(String prefName) { + if ((foreColorPref.get(prefName + FORE_COLOR_POSTFIX) != null) && (foreColorPref.get(prefName + FORE_COLOR_POSTFIX) instanceof ColorImpl)) + return (IColor) foreColorPref.get(prefName + FORE_COLOR_POSTFIX); + else + return ColorImpl.getSystemColor(SWT.COLOR_BLACK); + } + + /** + * Returns the chosen background color + * + * @return the background color + */ + @Override + public IColor getBackGroundColor(String prefName) { + if ((backColorPref.get(prefName + BACK_COLOR_POSTFIX) != null) && (backColorPref.get(prefName + BACK_COLOR_POSTFIX) instanceof ColorImpl)) + return (IColor) backColorPref.get(prefName + BACK_COLOR_POSTFIX); + else + return ColorImpl.getSystemColor(SWT.COLOR_WHITE); + } + + /** + * Returns the chosen font color + * + * @return the font color + */ + @Override + public IColor getFontColor(String prefName) { + if ((textColorPref.get(prefName + TEXT_COLOR_POSTFIX) != null) && (textColorPref.get(prefName + TEXT_COLOR_POSTFIX) instanceof ColorImpl)) + return (IColor) textColorPref.get(prefName + TEXT_COLOR_POSTFIX); + else + return ColorImpl.getSystemColor(SWT.COLOR_BLACK); + } + + /** + * Returns the foreground color to use for widget selection. This color is system dependent and not set using the + * preference page + * + * @return the foreground color + */ + @Override + public IColor getForeGroundColorSelection() { + if (noFocusSelection) + return ColorImpl.getSystemColor(SWT.COLOR_TITLE_INACTIVE_FOREGROUND); + return ColorImpl.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT); + } + + /** + * Returns the background color to use for widget selection. This color is system dependent and not set using the + * preference page + * + * @return the background color + */ + @Override + public IColor getBackGroundColorSelection() { + if (noFocusSelection) + return ColorImpl.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); + return ColorImpl.getSystemColor(SWT.COLOR_LIST_SELECTION); + } + + /** + * Returns the chosen font + * + * @return the font + */ + @Override + public IFont getFont(String prefName) { + if ((fontPref.get(prefName) != null) && (fontPref.get(prefName) instanceof IFont)) + return (IFont) fontPref.get(prefName); + else + return FontImpl.getSystemFont(); + } + + /** + * Returns the SwimLane width chosen + * + * @return the SwimLane width + */ + public int getLifelineWidth() { + return prefStore.getInt(PREF_LIFELINE_WIDTH); + } + + /** + * Returns if font linkage with zoom has been chosen + * + * @return true if checked false otherwise + */ + public boolean fontLinked() { + return prefStore.getBoolean(PREF_LINK_FONT); + } + + /** + * Returns the tooltip enablement + * + * @return true if checked false otherwise + */ + public boolean tooltipEnabled() { + return prefStore.getBoolean(PREF_TOOLTIP); + } + + /** + * Return true if the user do not want to take external time (basically found and lost messages with time) into + * account in the min max computation + * + * @return true if checked false otherwise + */ + public boolean excludeExternalTime() { + return prefStore.getBoolean(PREF_EXCLUDE_EXTERNAL_TIME); + } + + /** + * Returns if the use gradient color has been chosen + * + * @return true if checked false otherwise + */ + @Override + public boolean useGradienColor() { + return prefStore.getBoolean(PREF_USE_GRADIENT); + } + + /** + * Returns the color used to connect the time compression bar to the diagram graph node + * + * @return the selection color + */ + @Override + public IColor getTimeCompressionSelectionColor() { + return timeCompressionSelectionColor; + } + + /** + * Builds the new colors and fonts according the current user selection when the OK or Apply button is clicked + * + */ + protected void buildFontsAndColors() { + + Display display = Display.getDefault(); + + for (int i = 0; i < fontList.length; i++) { + FontData fontData = PreferenceConverter.getFontData(prefStore, fontList[i]); + if ((fontPref.get(fontList[i]) != null) && (fontPref.get(fontList[i]) instanceof IFont)) + ((IFont) fontPref.get(fontList[i])).dispose(); + fontPref.put(fontList[i], new FontImpl(display, fontData)); + } + + for (int i = 0; i < prefBackColorList.length; i++) { + RGB rgb = PreferenceConverter.getColor(prefStore, prefBackColorList[i] + BACK_COLOR_POSTFIX); + if ((backColorPref.get(prefBackColorList[i] + BACK_COLOR_POSTFIX) != null) && (backColorPref.get(prefBackColorList[i] + BACK_COLOR_POSTFIX) instanceof IColor)) + ((IColor) backColorPref.get(prefBackColorList[i] + BACK_COLOR_POSTFIX)).dispose(); + backColorPref.put(prefBackColorList[i] + BACK_COLOR_POSTFIX, new ColorImpl(display, rgb.red, rgb.green, rgb.blue)); + } + + for (int i = 0; i < prefForeColorList.length; i++) { + RGB rgb = PreferenceConverter.getColor(prefStore, prefForeColorList[i] + FORE_COLOR_POSTFIX); + if ((foreColorPref.get(prefForeColorList[i] + FORE_COLOR_POSTFIX) != null) && (foreColorPref.get(prefForeColorList[i] + FORE_COLOR_POSTFIX) instanceof IColor)) + ((IColor) foreColorPref.get(prefForeColorList[i] + FORE_COLOR_POSTFIX)).dispose(); + foreColorPref.put(prefForeColorList[i] + FORE_COLOR_POSTFIX, new ColorImpl(display, rgb.red, rgb.green, rgb.blue)); + } + + for (int i = 0; i < prefTextColorList.length; i++) { + RGB rgb = PreferenceConverter.getColor(prefStore, prefTextColorList[i] + TEXT_COLOR_POSTFIX); + if ((textColorPref.get(prefTextColorList[i] + TEXT_COLOR_POSTFIX) != null) && (textColorPref.get(prefTextColorList[i] + TEXT_COLOR_POSTFIX) instanceof IColor)) + ((IColor) textColorPref.get(prefTextColorList[i] + TEXT_COLOR_POSTFIX)).dispose(); + textColorPref.put(prefTextColorList[i] + TEXT_COLOR_POSTFIX, new ColorImpl(display, rgb.red, rgb.green, rgb.blue)); + } + + RGB rgb = PreferenceConverter.getColor(prefStore, PREF_TIME_COMP); + if (timeCompressionSelectionColor != null) + timeCompressionSelectionColor.dispose(); + timeCompressionSelectionColor = new ColorImpl(display, rgb.red, rgb.green, rgb.blue); + } + + public void addPropertyChangeListener(IPropertyChangeListener listener) { + prefStore.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(IPropertyChangeListener listener) { + prefStore.removePropertyChangeListener(listener); + } + + /* + * (non-Javadoc) + * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent) + */ + @Override + public void propertyChange(PropertyChangeEvent event) { + if (!event.getProperty().equals("PREFOK")) //$NON-NLS-1$ + { + buildFontsAndColors(); + prefStore.firePropertyChangeEvent("PREFOK", null, null); //$NON-NLS-1$ + } + } + + public void setNoFocusSelection(boolean v) { + noFocusSelection = v; + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/preferences/SDViewerPage.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/preferences/SDViewerPage.java new file mode 100755 index 0000000000..bf257d91c6 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/preferences/SDViewerPage.java @@ -0,0 +1,432 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: SDViewerPage.java,v 1.5 2008/01/24 02:28:51 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.preferences; + +import java.util.Iterator; +import java.util.Set; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.preference.BooleanFieldEditor; +import org.eclipse.jface.preference.ColorFieldEditor; +import org.eclipse.jface.preference.FontFieldEditor; +import org.eclipse.jface.preference.IntegerFieldEditor; +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.SDMessages; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.List; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; + +/** + * The Sequence Diagram preferences page implementation + * + * @author sveyrier + */ +public class SDViewerPage extends PreferencePage implements IWorkbenchPreferencePage, SelectionListener { + + /** + * The preference handler used to access the PreferenceStore + */ + protected SDViewPref pref = null; + /** + * BackGround color selector + */ + protected ColorFieldEditor lineColor = null; + /** + * Foreground color selector + */ + protected ColorFieldEditor backGroundColor = null; + + /** + * Font color selector + */ + protected ColorFieldEditor textColor = null; + + /** + * List which display all modifiable sequence Diagram font + */ + protected List classItemList = null; + + /** + * Font selector (The same is used for each modifiable font) + */ + protected FontFieldEditor font = null; + + /** + * Link font when zooming selector + */ + protected BooleanFieldEditor link = null; + + /** + * Enable tooltip selector + */ + protected BooleanFieldEditor tooltip = null; + + /** + * Do not take external time into account in the min max computation + */ + protected BooleanFieldEditor noExternalTime = null; + + /** + * Use gradient color selector + */ + protected BooleanFieldEditor useGrad = null; + + protected Composite buttonArea; + + /** + * SwimLane width selector + */ + protected IntegerFieldEditor lifelineWidth = null; + + protected static final String TEMP_TAG = SDViewPref.TEMP_TAG; + + public SDViewerPage() { + super(); + } + + /** + * Creates the Sequence Diagram preference page content + * + * @param parent the parent composite + */ + @Override + protected Control createContents(Composite parent) { + parent.setLayout(new GridLayout()); + Composite page = new Composite(parent, SWT.NONE); + GridLayout pageLayout = new GridLayout(); + pageLayout.numColumns = 2; + GridData pageLayoutdata = new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_VERTICAL | GridData.VERTICAL_ALIGN_FILL); + page.setLayoutData(pageLayoutdata); + page.setLayout(pageLayout); + + tooltip = new BooleanFieldEditor(SDViewPref.PREF_TOOLTIP, SDMessages._97, page); + tooltip.setPreferenceStore(pref.getPreferenceStore()); + tooltip.load(); + + // link font with zoom pref + link = new BooleanFieldEditor(SDViewPref.PREF_LINK_FONT, SDMessages._82, page); + link.setPreferenceStore(pref.getPreferenceStore()); + link.load(); + + noExternalTime = new BooleanFieldEditor(SDViewPref.PREF_EXCLUDE_EXTERNAL_TIME, SDMessages._83, page); + noExternalTime.setPreferenceStore(pref.getPreferenceStore()); + noExternalTime.load(); + + // use gradient color pref + useGrad = new BooleanFieldEditor(SDViewPref.PREF_USE_GRADIENT, SDMessages._84, page); + useGrad.setPreferenceStore(pref.getPreferenceStore()); + useGrad.load(); + + Label separator = new Label(page, SWT.SEPARATOR | SWT.HORIZONTAL | SWT.SHADOW_NONE); + GridData sepData = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL); + separator.setLayoutData(sepData); + + Composite prefPage = new Composite(page, SWT.NONE); + GridLayout prefPageLayout = new GridLayout(); + prefPage.setLayoutData(pageLayoutdata); + prefPageLayout.numColumns = 1; + prefPage.setLayout(prefPageLayout); + + // swimLane width pref + lifelineWidth = new IntegerFieldEditor(SDViewPref.PREF_LIFELINE_WIDTH, SDMessages._80, prefPage); + lifelineWidth.setPreferenceStore(pref.getPreferenceStore()); + lifelineWidth.setValidRange(119, 500); + lifelineWidth.load(); + + // not very nice + new Label(prefPage, SWT.SEPARATOR | SWT.HORIZONTAL | SWT.SHADOW_NONE); + new Label(prefPage, SWT.SEPARATOR | SWT.HORIZONTAL | SWT.SHADOW_NONE); + + // Font list pref + classItemList = new List(prefPage, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); + GridData tabItemLayoutdata = new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_VERTICAL | GridData.VERTICAL_ALIGN_FILL); + classItemList.setLayoutData(tabItemLayoutdata); + + for (int i = 0; i < SDViewPref.fontList2.length; i++) { + classItemList.add(SDViewPref.fontList2[i]); + } + classItemList.setSelection(0); + classItemList.addSelectionListener(this); + buttonArea = new Composite(prefPage, SWT.NONE); + GridData tabItemLayoutdata2 = new GridData(GridData.HORIZONTAL_ALIGN_FILL/* |GridData.GRAB_HORIZONTAL */| GridData.GRAB_VERTICAL | GridData.VERTICAL_ALIGN_FILL); + buttonArea.setLayoutData(tabItemLayoutdata2); + GridLayout buttonAreaLayout = new GridLayout(); + buttonAreaLayout.numColumns = 1; + buttonArea.setLayout(buttonAreaLayout); + + // font selector initialise for the lifeline font pref + font = new FontFieldEditor(SDViewPref.fontList[0], "",//$NON-NLS-1$ + SDMessages._81, buttonArea); + font.getPreviewControl().setSize(500, 500); + font.setPreferenceStore(pref.getPreferenceStore()); + font.load(); + + backGroundColor = new ColorFieldEditor(SDViewPref.fontList[0] + SDViewPref.BACK_COLOR_POSTFIX, SDMessages._85, buttonArea); + backGroundColor.setPreferenceStore(pref.getPreferenceStore()); + backGroundColor.load(); + + lineColor = new ColorFieldEditor(SDViewPref.fontList[0] + SDViewPref.FORE_COLOR_POSTFIX, SDMessages._86, buttonArea); + lineColor.setPreferenceStore(pref.getPreferenceStore()); + lineColor.load(); + + textColor = new ColorFieldEditor(SDViewPref.fontList[0] + SDViewPref.TEXT_COLOR_POSTFIX, SDMessages._87, buttonArea); + textColor.setPreferenceStore(pref.getPreferenceStore()); + textColor.load(); + swapPref(true); + Dialog.applyDialogFont(page); + + return page; + } + + @Override + public void init(IWorkbench workbench) { + pref = SDViewPref.getInstance(); + } + + /** + * Apply the modification performed in the Sequence diagram preference page + */ + @Override + protected void performApply() { + // Store the prefrences in the PreferenceStore + if (!lifelineWidth.isValid()) { + lifelineWidth.showErrorMessage(); + return; + } + font.store(); + backGroundColor.store(); + lineColor.store(); + link.store(); + tooltip.store(); + noExternalTime.store(); + textColor.store(); + useGrad.store(); + lifelineWidth.store(); + swapPref(false); + // then save them in the preference file + pref.apply(); + swapPref(true); + } + + /** + * Apply the modification performed in the Sequence diagram preference page + * + * @return true + */ + @Override + public boolean performOk() { + performApply(); + return true; + } + + /** + * Loads the Sequence diagram default preference settings + */ + @Override + protected void performDefaults() { + link.loadDefault(); + tooltip.loadDefault(); + noExternalTime.loadDefault(); + useGrad.loadDefault(); + lifelineWidth.loadDefault(); + + // and all the fonts and colors + // fonts and colors are stored for each time because + // we are using only one FontFieldEditor + Set keySet = SDViewPref.getInstance().fontPref.keySet(); + Iterator it = keySet.iterator(); + while (it.hasNext()) { + Object prefName = it.next(); + if (prefName instanceof String) { + font.setPreferenceName((String) prefName); + font.loadDefault(); + font.setPreferenceName((String) prefName + TEMP_TAG); + font.store(); + } + } + + keySet = SDViewPref.getInstance().backColorPref.keySet(); + it = keySet.iterator(); + while (it.hasNext()) { + Object prefName = it.next(); + if (prefName instanceof String) { + backGroundColor.setPreferenceName((String) prefName); + backGroundColor.loadDefault(); + backGroundColor.setPreferenceName((String) prefName + TEMP_TAG); + backGroundColor.store(); + } + + } + + backGroundColor.setPreferenceName(SDViewPref.fontList[classItemList.getSelectionIndex()] + SDViewPref.BACK_COLOR_POSTFIX + TEMP_TAG); + backGroundColor.load(); + + keySet = SDViewPref.getInstance().foreColorPref.keySet(); + it = keySet.iterator(); + while (it.hasNext()) { + Object prefName = it.next(); + if (prefName instanceof String) { + lineColor.setPreferenceName((String) prefName); + lineColor.loadDefault(); + lineColor.setPreferenceName((String) prefName + TEMP_TAG); + lineColor.store(); + } + } + + lineColor.setPreferenceName(SDViewPref.fontList[classItemList.getSelectionIndex()] + SDViewPref.FORE_COLOR_POSTFIX + TEMP_TAG); + lineColor.load(); + + keySet = SDViewPref.getInstance().textColorPref.keySet(); + it = keySet.iterator(); + while (it.hasNext()) { + Object prefName = it.next(); + if (prefName instanceof String) { + textColor.setPreferenceName((String) prefName); + textColor.loadDefault(); + textColor.setPreferenceName((String) prefName + TEMP_TAG); + textColor.store(); + } + } + textColor.setPreferenceName(SDViewPref.fontList[classItemList.getSelectionIndex()] + SDViewPref.TEXT_COLOR_POSTFIX + TEMP_TAG); + textColor.load(); + } + + /** + * Sent when a new selection occurs in the graphNode font list + * + * @param e the selection event + */ + @Override + public void widgetSelected(SelectionEvent e) { + // Store the past set font preference or else the + // FontFieldEditor reassignment will make us loose the current modification + font.store(); + lineColor.store(); + backGroundColor.store(); + textColor.store(); + + // set the FontFieldEditor for the new selected graphNode font + font.setPreferenceName(SDViewPref.fontList[classItemList.getSelectionIndex()] + TEMP_TAG); + font.load(); + + backGroundColor.setPreferenceName(SDViewPref.fontList[classItemList.getSelectionIndex()] + SDViewPref.BACK_COLOR_POSTFIX + TEMP_TAG); + backGroundColor.load(); + + lineColor.setPreferenceName(SDViewPref.fontList[classItemList.getSelectionIndex()] + SDViewPref.FORE_COLOR_POSTFIX + TEMP_TAG); + lineColor.load(); + + textColor.setPreferenceName(SDViewPref.fontList[classItemList.getSelectionIndex()] + SDViewPref.TEXT_COLOR_POSTFIX + TEMP_TAG); + textColor.load(); + + // No Background for message graphNodes + if ((SDViewPref.fontList[classItemList.getSelectionIndex()].equals(SDViewPref.PREF_SYNC_MESS)) || (SDViewPref.fontList[classItemList.getSelectionIndex()].equals(SDViewPref.PREF_SYNC_MESS_RET)) + || (SDViewPref.fontList[classItemList.getSelectionIndex()].equals(SDViewPref.PREF_ASYNC_MESS)) || (SDViewPref.fontList[classItemList.getSelectionIndex()].equals(SDViewPref.PREF_ASYNC_MESS_RET))) + backGroundColor.setEnabled(false, buttonArea); + else + backGroundColor.setEnabled(true, buttonArea); + + // No font used for execution occurrence and global frame + if ((SDViewPref.fontList[classItemList.getSelectionIndex()].equals(SDViewPref.PREF_EXEC)) || (SDViewPref.fontList[classItemList.getSelectionIndex()].equals(SDViewPref.PREF_FRAME))) + textColor.setEnabled(false, buttonArea); + else + textColor.setEnabled(true, buttonArea); + + if (SDViewPref.fontList[classItemList.getSelectionIndex()].equals(SDViewPref.PREF_FRAME)) + font.setEnabled(false, buttonArea); + else + font.setEnabled(true, buttonArea); + } + + protected void swapPref(boolean toTemp) { + String TAG1 = "";//$NON-NLS-1$ + String TAG2 = TEMP_TAG; + if (!toTemp) { + TAG1 = TEMP_TAG; + TAG2 = "";//$NON-NLS-1$ + } + Set keySet = SDViewPref.getInstance().fontPref.keySet(); + Iterator it = keySet.iterator(); + while (it.hasNext()) { + Object prefName = it.next(); + if (prefName instanceof String) { + font.setPreferenceName((String) prefName + TAG1); + font.load(); + font.setPreferenceName((String) prefName + TAG2); + font.store(); + } + } + + keySet = SDViewPref.getInstance().backColorPref.keySet(); + it = keySet.iterator(); + while (it.hasNext()) { + Object prefName = it.next(); + if (prefName instanceof String) { + backGroundColor.setPreferenceName((String) prefName + TAG1); + backGroundColor.load(); + backGroundColor.setPreferenceName((String) prefName + TAG2); + backGroundColor.store(); + } + + } + + keySet = SDViewPref.getInstance().foreColorPref.keySet(); + it = keySet.iterator(); + while (it.hasNext()) { + Object prefName = it.next(); + if (prefName instanceof String) { + lineColor.setPreferenceName((String) prefName + TAG1); + lineColor.load(); + lineColor.setPreferenceName((String) prefName + TAG2); + lineColor.store(); + } + } + + keySet = SDViewPref.getInstance().textColorPref.keySet(); + it = keySet.iterator(); + while (it.hasNext()) { + Object prefName = it.next(); + if (prefName instanceof String) { + textColor.setPreferenceName((String) prefName + TAG1); + textColor.load(); + textColor.setPreferenceName((String) prefName + TAG2); + textColor.store(); + } + } + if (toTemp) { + // set the FontFieldEditor for the new selected graphNode font + font.setPreferenceName(SDViewPref.fontList[classItemList.getSelectionIndex()] + TEMP_TAG); + font.load(); + + backGroundColor.setPreferenceName(SDViewPref.fontList[classItemList.getSelectionIndex()] + SDViewPref.BACK_COLOR_POSTFIX + TEMP_TAG); + backGroundColor.load(); + + lineColor.setPreferenceName(SDViewPref.fontList[classItemList.getSelectionIndex()] + SDViewPref.FORE_COLOR_POSTFIX + TEMP_TAG); + lineColor.load(); + + textColor.setPreferenceName(SDViewPref.fontList[classItemList.getSelectionIndex()] + SDViewPref.TEXT_COLOR_POSTFIX + TEMP_TAG); + textColor.load(); + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/MinMaxDialog.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/MinMaxDialog.java new file mode 100755 index 0000000000..cd2484714f --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/MinMaxDialog.java @@ -0,0 +1,166 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: MinMaxDialog.java,v 1.2 2008/01/24 02:28:51 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.util; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * @author sveyrier + * + */ +public class MinMaxDialog extends Dialog { + + protected Label minLabel; + + protected Label maxLabel; + + protected Label scaleLabel; + + protected Label precisionLabel; + + protected Text minText; + + protected Text maxText; + + protected Text scaleText; + + protected Text precisionText; + + SDWidget sdWidget; + + public MinMaxDialog(Shell s, SDWidget v) { + super(s); + sdWidget = v; + } + + protected GridData newGridData(int span) { + GridData data = new GridData(GridData.GRAB_VERTICAL | GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL); + data.horizontalSpan = span; + return data; + } + + @Override + protected Control createDialogArea(Composite p) { + p.getShell().setText(SDMessages._123); + Composite parent = (Composite) super.createDialogArea(p); + + GridLayout parentLayout = new GridLayout(); + parentLayout.numColumns = 6; + parent.setLayout(parentLayout); + + Group g1 = new Group(parent, SWT.SHADOW_NONE); + g1.setLayoutData(newGridData(3)); + GridLayout g1layout = new GridLayout(); + g1layout.numColumns = 3; + g1.setLayout(g1layout); + + minLabel = new Label(g1, SWT.RADIO); + minLabel.setText(SDMessages._124); + minLabel.setLayoutData(newGridData(1)); + + minText = new Text(g1, SWT.SINGLE | SWT.BORDER); + minText.setLayoutData(newGridData(2)); + minText.setText(String.valueOf(sdWidget.getFrame().getMinTime().getValue())); + + maxLabel = new Label(g1, SWT.RADIO); + maxLabel.setText(SDMessages._125); + maxLabel.setLayoutData(newGridData(1)); + + maxText = new Text(g1, SWT.SINGLE | SWT.BORDER); + maxText.setLayoutData(newGridData(2)); + maxText.setText(String.valueOf(sdWidget.getFrame().getMaxTime().getValue())); + + scaleLabel = new Label(g1, SWT.RADIO); + scaleLabel.setText(SDMessages._136); + scaleLabel.setLayoutData(newGridData(1)); + + scaleText = new Text(g1, SWT.SINGLE | SWT.BORDER); + scaleText.setLayoutData(newGridData(2)); + scaleText.setText(String.valueOf(sdWidget.getFrame().getMinTime().getScale())); + + + precisionLabel = new Label(g1, SWT.RADIO); + precisionLabel.setText(SDMessages._137); + precisionLabel.setLayoutData(newGridData(1)); + + precisionText = new Text(g1, SWT.SINGLE | SWT.BORDER); + precisionText.setLayoutData(newGridData(2)); + precisionText.setText(String.valueOf(sdWidget.getFrame().getMinTime().getPrecision())); + + return parent; + } + + @Override + protected void okPressed() { + // double min=0; + // double max=0; + long min = 0; + long max = 0; + byte scale = 0; + long precision = 0; + try { + min = Long.parseLong(minText.getText()); + max = Long.parseLong(maxText.getText()); + scale = Byte.parseByte(scaleText.getText()); + precision = Long.parseLong(precisionText.getText()); + + sdWidget.getFrame().setMax(new TmfTimestamp(max, scale, precision)); + sdWidget.getFrame().setMin(new TmfTimestamp(min, scale, precision)); + + sdWidget.redraw(); + + super.okPressed(); + } catch (Exception e) { + MessageDialog.openError(getShell(), SDMessages._98, SDMessages._99); + } + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + super.createButtonsForButtonBar(parent); + createButton(parent, IDialogConstants.CLIENT_ID, SDMessages._126, false); + getButton(IDialogConstants.CLIENT_ID).addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + sdWidget.getFrame().resetCustomMinMax(); + minText.setText(String.valueOf(sdWidget.getFrame().getMinTime().getValue())); + maxText.setText(String.valueOf(sdWidget.getFrame().getMaxTime().getValue())); + scaleText.setText(String.valueOf(sdWidget.getFrame().getMinTime().getScale())); + precisionText.setText(String.valueOf(sdWidget.getFrame().getMinTime().getPrecision())); + maxText.getParent().layout(true); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + + } + + }); + } +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SDMessages.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SDMessages.java new file mode 100755 index 0000000000..8f5e318d1f --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SDMessages.java @@ -0,0 +1,175 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Bernd Hufmann - Updated for TMF + *******************************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.util; + +import org.eclipse.osgi.util.NLS; + +public final class SDMessages extends NLS { + + protected static final String BUNDLE_NAME = "org.eclipse.linuxtools.tmf.ui.views.uml2sd.util.messages";//$NON-NLS-1$ + + protected SDMessages() { + // Do not instantiate + } + + public static String _1; + public static String _2; + public static String _3; + public static String _4; + public static String _5; + public static String _6; + public static String _7; + public static String _8; + public static String _9; + public static String _10; + public static String _11; + public static String _12; + public static String _13; + public static String _14; + public static String _15; + public static String _16; + public static String _17; + public static String _18; + public static String _19; + public static String _20; + public static String _21; + public static String _22; + public static String _23; + public static String _24; + public static String _25; + public static String _26; + public static String _27; + public static String _28; + public static String _29; + public static String _30; + public static String _31; + public static String _32; + public static String _33; + public static String _34; + public static String _35; + public static String _36; + public static String _37; + public static String _38; + public static String _39; + public static String _40; + public static String _41; + public static String _42; + public static String _43; + public static String _44; + public static String _45; + public static String _46; + public static String _47; + public static String _48; + public static String _49; + public static String _50; + public static String _51; + public static String _52; + public static String _53; + public static String _54; + public static String _55; + public static String _56; + public static String _57; + public static String _58; + public static String _59; + public static String _60; + public static String _61; + public static String _62; + public static String _63; + public static String _64; + public static String _65; + public static String _66; + public static String _67; + public static String _68; + public static String _69; + public static String _70; + public static String _71; + public static String _72; + public static String _73; + public static String _74; + public static String _75; + public static String _76; + public static String _77; + public static String _78; + public static String _79; + public static String _80; + public static String _81; + public static String _82; + public static String _83; + public static String _84; + public static String _85; + public static String _86; + public static String _87; + public static String _88; + public static String _89; + public static String _90; + public static String _91; + public static String _92; + public static String _93; + public static String _94; + public static String _95; + public static String _96; + public static String _97; + public static String _98; + public static String _99; + public static String _100; + public static String _101; + public static String _102; + public static String _103; + public static String _104; + public static String _105; + public static String _106; + public static String _107; + public static String _108; + public static String _109; + public static String _110; + public static String _111; + public static String _112; + public static String _113; + public static String _114; + public static String _115; + public static String _116; + public static String _117; + public static String _118; + public static String _119; + public static String _120; + public static String _121; + public static String _122; + public static String _123; + public static String _124; + public static String _125; + public static String _126; + public static String _127; + public static String _128; + public static String _129; + public static String _130; + public static String _131; + public static String _132; + public static String _133; + public static String _134; + public static String _135; + public static String _136; + public static String _137; + public static String _138; + + public static String _139; + public static String _140; + public static String _141; + public static String _142; + + public static String uml_23; + public static String uml_25; + public static String uml_45; + + static { + NLS.initializeMessages(BUNDLE_NAME, SDMessages.class); + } +} \ No newline at end of file diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SDPrintDialog.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SDPrintDialog.java new file mode 100755 index 0000000000..2b2a72ca53 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SDPrintDialog.java @@ -0,0 +1,130 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: SDPrintDialog.java,v 1.3 2008/01/24 02:28:52 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ + +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.util; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; + +/** + * @author sveyrier + */ +public class SDPrintDialog extends Dialog { + + protected SDWidget view; + + protected SDPrintDialogUI dialogUI; + + protected String errorMessage = null; + protected Label messageLabel = null; + protected boolean isPageComplete = true; + + public SDPrintDialog(Shell s, SDWidget v) { + super(s); + view = v; + + dialogUI = new SDPrintDialogUI(s, view); + dialogUI.setParentDialog(this); + } + + @Override + protected Control createDialogArea(Composite p) { + p.getShell().setText(SDMessages._114); + Composite parent = (Composite) super.createDialogArea(p); + + dialogUI.createDialogArea(parent); + + // bug 195026 + messageLabel = new Label(parent, SWT.NONE); + GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); + gridData.horizontalSpan = 6; + messageLabel.setLayoutData(gridData); + setErrorMessage(errorMessage); + + return parent; + } + + @Override + protected void okPressed() { + + if (dialogUI.okPressed()) + super.okPressed(); + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + + super.createButtonsForButtonBar(parent); + createButton(parent, IDialogConstants.CLIENT_ID, SDMessages._115, false); + + getButton(IDialogConstants.CLIENT_ID).addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + + dialogUI.printButtonSelected(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + + }); + + updateButtons(); + } + + public SDPrintDialogUI getDialogUI() { + return dialogUI; + } + + public void setErrorMessage(String message) { + errorMessage = message; + if (messageLabel != null) { + if (errorMessage == null) { + messageLabel.setText(""); //$NON-NLS-1$ + } else { + messageLabel.setText(errorMessage); + } + } + } + + public void setPageComplete(boolean complete) { + isPageComplete = complete; + updateButtons(); + } + + public void updateButtons() { + Button okButton = getButton(IDialogConstants.OK_ID); + if (isPageComplete) { + if (okButton != null) { + okButton.setEnabled(true); + } + } else { + if (okButton != null) { + okButton.setEnabled(false); + } + } + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SDPrintDialogUI.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SDPrintDialogUI.java new file mode 100755 index 0000000000..4c9cc6f2b9 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SDPrintDialogUI.java @@ -0,0 +1,1102 @@ +/********************************************************************** + * Copyright (c) 2005, 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: SDPrintDialogUI.java,v 1.3 2008/01/24 02:28:52 apnan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.util; + +import java.text.MessageFormat; +import java.util.Arrays; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.linuxtools.tmf.ui.viewers.timeAnalysis.GridUtil; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.DiagramToolTip; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.NGC; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.SDWidget; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseTrackListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.printing.PrintDialog; +import org.eclipse.swt.printing.Printer; +import org.eclipse.swt.printing.PrinterData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * @author sveyrier + */ +public class SDPrintDialogUI { + + protected Button setHPagesNumber; + + protected Button setVPagesNumber; + + protected Button useCurrentZoom; + + protected Button allPages; + + protected Button currentPage; + + protected Button pageList; + + protected Button pageRange; + + protected Text fromPage; + + protected Text toPage; + + protected SDWidget view; + + protected Text hPagesNum; + + protected Text vPagesNum; + + protected Text totalPages; + + protected ModifyListener modifyListener; + + protected SelectionListener selectionListener; + + protected LocalSD overviewCanvas; + + protected int nbPages = 0; + + protected int pageNum = -1; + + protected int firstPage = -1; + + protected int pagesList[]; + + protected float stepX, stepY, sTX, sTY; + + protected int from, to; + + protected boolean multiSelection = false; + + protected boolean areaSelection = false; + + protected boolean printAll; + protected boolean printCurrent; + protected boolean printSelection; + protected boolean printRange; + + protected int nbRows, nbLines; + + protected float zoomFactor; + + protected PrinterData printerData; + + protected DiagramToolTip toolTip = null; + + protected Label currentSelection; + + protected Shell shell; + + protected Button printerDialog; + + protected boolean showPrintButton; + + int test = 3; + + public static int r = 0; + + // bug 195026 + protected WizardPage parentWizardPage = null; + protected SDPrintDialog parentDialog = null; + + protected class LocalSD extends SDWidget { + + @Override + public int getContentsHeight() { + if (view.getContentsHeight() > view.getContentsHeight()) + return (int) (view.getVisibleHeight() / (float) test / view.zoomValue); + else + return (int) (super.getContentsHeight()); + } + + @Override + public int getContentsWidth() { + if (view.getVisibleWidth() > view.getContentsWidth()) + return (int) (view.getVisibleWidth() / (float) test / view.zoomValue); + else + return (int) (super.getContentsWidth()); + } + + public LocalSD(Composite c, int s) { + super(c, s); + } + + @Override + protected void contentsMouseHover(MouseEvent event) { + } + + protected Image createPagesSelectionImages(Image img, int width, float stepX, int height, float stepY) { + + Image over = new Image(super.getShell().getDisplay(), img.getImageData()); + + for (int pageIndex = 0; pageIndex < pagesList.length; pageIndex++) { + /* + * int line=pageNum/getPagesForSelection(); int row=pageNum%getPagesForSelection(); if (row!=0) line++; + * else row=getPagesForSelection(); line--; row--; over=new + * Image(super.getShell().getDisplay(),drawRegionSelected(img,new Rectangle(row*stepX, + * line*stepY,stepX,stepY),new RGB(0,155,0))); + */ + + int pageNum = pagesList[pageIndex]; + if (getPagesForSelection() > 0 && pageNum > 0) { + int line = pageNum / getNbRow(); + int row = pageNum % getNbRow(); + if (row != 0) + line++; + else + row = getNbRow(); + + line--; + row--; + + Image toDel = over; + if (overviewCanvas.isFocusControl()) + over = new Image(super.getShell().getDisplay(), drawRegionSelected(toDel, new Rectangle(contentsToViewX((int) (row * stepX * overviewCanvas.zoomValue)), contentsToViewY((int) (line * stepY * overviewCanvas.zoomValue)), + ((int) (stepX * overviewCanvas.zoomValue)), ((int) (stepY * overviewCanvas.zoomValue))), new RGB(0, 0, 128))); + else + over = new Image(super.getShell().getDisplay(), drawRegionSelected(toDel, new Rectangle(contentsToViewX((int) (row * stepX * overviewCanvas.zoomValue)), contentsToViewY((int) (line * stepY * overviewCanvas.zoomValue)), + ((int) (stepX * overviewCanvas.zoomValue)), ((int) (stepY * overviewCanvas.zoomValue))), new RGB(221, 208, 200))); + toDel.dispose(); + } + } + + Arrays.sort(pagesList); + int pos = Arrays.binarySearch(pagesList, pageNum); + if (pos < 0) + if (getPagesForSelection() > 0 && pageNum > 0) { + int line = pageNum / getNbRow(); + int row = pageNum % getNbRow(); + if (row != 0) + line++; + else + row = getNbRow(); + + line--; + row--; + + Image toDel = over; + over = new Image(super.getShell().getDisplay(), drawRegionSelected(toDel, new Rectangle(contentsToViewX((int) (row * stepX * overviewCanvas.zoomValue)), contentsToViewY((int) (line * stepY * overviewCanvas.zoomValue)), + ((int) (stepX * overviewCanvas.zoomValue)), ((int) (stepY * overviewCanvas.zoomValue))), new RGB(221, 208, 200))); + toDel.dispose(); + } + + GC imGC2 = new GC(over); + imGC2.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); + NGC imGC = new NGC(overviewCanvas, imGC2); + for (int i = 0, x = 0; x <= width && stepX > 0; i++, x = (int) (i * stepX)) + imGC.drawLine(x, 0, x, height); + + for (int j = 0, y = 0; y <= height && stepY > 0; j++, y = (int) (j * stepY)) + imGC.drawLine(0, y, width, y); + + // imGC2.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLUE)); + // // //imGC.drawLine(0, height, width, (int)(getVisibleHeight()/zoomValue)); + // imGC.fillRectangle(0,height,(int)(getVisibleWidth()/zoomValue), + // (int)(getVisibleHeight()/zoomValue)-height); + + imGC2.dispose(); + imGC.dispose(); + return over; + } + + @Override + protected void drawContents(GC gc, int clipx, int clipy, int clipw, int cliph) { + + Image dbuffer = getDrawBuffer(gc); + computeStepXY(); + Image d; + + int lw = (int) (getContentsWidth() / zoomValue); + if (getContentsWidth() < getVisibleWidth()) + lw = (int) (getVisibleWidth() / zoomValue); + + int lh = (int) (getContentsHeight() / zoomValue); + if (getContentsHeight() < getVisibleHeight()) + lh = (int) (getVisibleHeight() / zoomValue); + d = createPagesSelectionImages(dbuffer, lw, stepX, lh, stepY); + + if (!isEnabled()) { + Image toDel = d; + d = new Image(super.getShell().getDisplay(), drawRegionSelected(d, new Rectangle(0, 0, lw, lh), new RGB(221, 208, 200))); + // d, new Rectangle(0,0,((int)(stepX*overviewCanvas.zoomValue)),((int)( + // stepY*overviewCanvas.zoomValue))),new RGB(221,208,200))); + toDel.dispose(); + } + + Rectangle area = getClientArea(); + int w = d.getBounds().width; + int h = d.getBounds().height; + gc.drawImage(d, 0, 0, w, h, 0, 0, area.width, area.height); + + totalPages.setText(Integer.valueOf(maxNumOfPages()).toString()); + displayPageNum(); + dbuffer.dispose(); + d.dispose(); + gc.dispose(); + + } + + @Override + protected void keyPressedEvent(KeyEvent e) { + if (e.keyCode == SWT.CTRL) + multiSelection = true; + if (e.keyCode == SWT.SHIFT) + areaSelection = true; + if (e.keyCode == SWT.ARROW_DOWN) { + if (pageNum + getNbRow() <= maxNumOfPages()) + pageNum += getNbRow(); + int line = pageNum / getNbRow(); + int row = pageNum % getNbRow(); + if (row == 0) + line--; + if ((line + 1) * stepY > (overviewCanvas.getContentsY() + overviewCanvas.getVisibleHeight()) / overviewCanvas.zoomValue) + overviewCanvas.scrollBy(0, (int) (stepY * overviewCanvas.zoomValue)); + } + if (e.keyCode == SWT.ARROW_UP) { + if (pageNum - getNbRow() > 0) + pageNum -= getNbRow(); + int line = pageNum / getNbRow(); + int row = pageNum % getNbRow(); + if (row == 0) + line--; + if ((line) * stepY <= overviewCanvas.getContentsY() / overviewCanvas.zoomValue) + overviewCanvas.scrollBy(0, -(int) (stepY * overviewCanvas.zoomValue)); + } + if (e.keyCode == SWT.ARROW_LEFT) { + if ((pageNum - 2) / getNbRow() == (pageNum - 1) / getNbRow() && pageNum > 1) + pageNum--; + int row = pageNum % getNbRow(); + if ((row - 1) * stepX < (overviewCanvas.getContentsX()) / overviewCanvas.zoomValue) + overviewCanvas.scrollBy(-(int) (stepX * overviewCanvas.zoomValue), 0); + } + if (e.keyCode == SWT.ARROW_RIGHT) { + if ((pageNum - 1) / getNbRow() == pageNum / getNbRow()) + pageNum++; + int row = pageNum % getNbRow(); + if (row == 0) + row = getNbRow(); + if ((row) * stepX > (overviewCanvas.getContentsX() + overviewCanvas.getVisibleWidth()) / overviewCanvas.zoomValue) + overviewCanvas.scrollBy((int) (stepX * overviewCanvas.zoomValue), 0); + } + + if (e.keyCode == 32 && pageNum > -1) { + Arrays.sort(pagesList); + int pos = Arrays.binarySearch(pagesList, pageNum); + if (pos < 0) + addToPagesList(pageNum); + else + removeFromPagesList(pageNum); + } + + if (!areaSelection && !multiSelection) { + firstPage = pageNum; + pagesList = new int[1]; + pagesList[0] = pageNum; + } else if ((pageNum != -1) && (areaSelection) && (firstPage != -1)) { + pagesList = new int[0]; + int line1 = firstPage / getNbRow(); + int row1 = firstPage % getNbRow(); + if (row1 != 0) + line1++; + else + row1 = getNbRow(); + + int line2 = pageNum / getNbRow(); + int row2 = pageNum % getNbRow(); + if (row2 != 0) + line2++; + else + row2 = getNbRow(); + + int temp; + if (line1 > line2) { + temp = line2; + line2 = line1; + line1 = temp; + } + + if (row1 > row2) { + temp = row2; + row2 = row1; + row1 = temp; + } + + for (int i = row1 - 1; i < row2; i++) + for (int j = line1 - 1; j < line2; j++) + addToPagesList(i + j * getNbRow() + 1); + + } + displayPageNum(); + overviewCanvas.redraw(); + } + + @Override + protected void keyReleasedEvent(KeyEvent e) { + if (e.keyCode == SWT.CTRL) + multiSelection = false; + if (e.keyCode == SWT.SHIFT) + areaSelection = false; + } + + @Override + protected void contentsMouseDownEvent(MouseEvent event) { + + computeStepXY(); + int x1 = (int) ((event.x / overviewCanvas.zoomValue) / stepX); + int x2 = (int) ((event.y / overviewCanvas.zoomValue) / stepY); + + int oldPage = pageNum; + + pageNum = x1 + x2 * getNbRow() + 1; + + if (pageNum > maxNumOfPages()) { + pageNum = oldPage; + return; + } + + if (!areaSelection) + firstPage = pageNum; + + if ((pageNum != -1) && (multiSelection)) { + Arrays.sort(pagesList); + int pos = Arrays.binarySearch(pagesList, pageNum); + if (pos < 0) + addToPagesList(pageNum); + else + removeFromPagesList(pageNum); + } else if ((pageNum != -1) && (areaSelection) && (firstPage != -1)) { + + pagesList = new int[0]; + + int line1 = firstPage / getNbRow(); + int row1 = firstPage % getNbRow(); + if (row1 != 0) + line1++; + else + row1 = getNbRow(); + + int line2 = pageNum / getNbRow(); + int row2 = pageNum % getNbRow(); + if (row2 != 0) + line2++; + else + row2 = getNbRow(); + + int temp; + if (line1 > line2) { + temp = line2; + line2 = line1; + line1 = temp; + } + + if (row1 > row2) { + temp = row2; + row2 = row1; + row1 = temp; + } + + for (int i = row1 - 1; i < row2; i++) + for (int j = line1 - 1; j < line2; j++) + addToPagesList(i + j * getNbRow() + 1); + } else { + pagesList = new int[1]; + pagesList[0] = pageNum; + } + if ((event.stateMask & SWT.CTRL) != 0) + multiSelection = true; + displayPageNum(); + redraw(); + } + + @Override + protected void contentsMouseMoveEvent(MouseEvent e) { + toolTip.hideToolTip(); + } + + @Override + public void resizeContents(int _w, int _h) { + // if (r<2) + super.resizeContents(_w, _h); + r++; + } + + } + + public SDPrintDialogUI(Shell s, SDWidget v) { + + this(s, v, false); + + } + + public SDPrintDialogUI(Shell s, SDWidget v, boolean showPrintBtn) { + + setShell(s); + view = v; + showPrintButton = showPrintBtn; + + printerData = Printer.getDefaultPrinterData(); + if (printerData != null) { + printerData.scope = PrinterData.SELECTION; + } + + pagesList = new int[0]; + + selectionListener = new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + if (useCurrentZoom.getSelection()) { + hPagesNum.setEnabled(false); + vPagesNum.setEnabled(false); + } + if (setHPagesNumber.getSelection()) { + hPagesNum.setEnabled(true); + vPagesNum.setEnabled(false); + if (currentPage.getSelection()) { + currentPage.setSelection(false); + allPages.setSelection(true); + } + if (hPagesNum.getText() == "") //$NON-NLS-1$ + hPagesNum.setText("1"); //$NON-NLS-1$ + } + if (setVPagesNumber.getSelection()) { + hPagesNum.setEnabled(false); + vPagesNum.setEnabled(true); + if (currentPage.getSelection()) { + currentPage.setSelection(false); + allPages.setSelection(true); + } + if (vPagesNum.getText() == "") //$NON-NLS-1$ + vPagesNum.setText("1"); //$NON-NLS-1$ + } + if (currentPage.getSelection() || allPages.getSelection() || pageList.getSelection()) { + fromPage.setEnabled(false); + toPage.setEnabled(false); + } else { + fromPage.setEnabled(true); + toPage.setEnabled(true); + } + + currentPage.setEnabled(useCurrentZoom.getSelection()); + overviewCanvas.setEnabled(pageList.getSelection()); + if (overviewCanvas.isEnabled() && (e.widget == useCurrentZoom || e.widget == setHPagesNumber || e.widget == setVPagesNumber)) { + pagesList = new int[1]; + pagesList[0] = 1; + pageNum = 1; + firstPage = 1; + } else if (overviewCanvas.isEnabled() && (e.widget == pageList)) { + if (pagesList == null || pagesList.length <= 0) { + pagesList = new int[1]; + pagesList[0] = 1; + pageNum = 1; + firstPage = 1; + } + } + computeStepXY(); + totalPages.setText(Integer.valueOf(maxNumOfPages()).toString()); + overviewCanvas.redraw(); + overviewCanvas.update(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + pagesList = new int[0]; + computeStepXY(); + overviewCanvas.redraw(); + } + + }; + + modifyListener = new ModifyListener() { + + @Override + public void modifyText(ModifyEvent e) { + pagesList = new int[0]; + computeStepXY(); + totalPages.setText(Integer.valueOf(maxNumOfPages()).toString()); + overviewCanvas.redraw(); + } + + }; + } + + protected GridData newGridData(int span) { + GridData data = new GridData(GridData.GRAB_VERTICAL | GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL); + data.horizontalSpan = span; + return data; + } + + public Control createDialogArea(Composite parent) { + + GridLayout parentLayout = new GridLayout(); + parentLayout.numColumns = 6; + parent.setLayout(parentLayout); + + Group g1 = new Group(parent, SWT.SHADOW_NONE); + g1.setText(SDMessages._113); + g1.setLayoutData(newGridData(3)); + GridLayout g1layout = new GridLayout(); + g1layout.numColumns = 2; + g1.setLayout(g1layout); + + useCurrentZoom = new Button(g1, SWT.RADIO); + useCurrentZoom.setText(SDMessages._112); + useCurrentZoom.setLayoutData(newGridData(2)); + useCurrentZoom.addSelectionListener(selectionListener); + + setHPagesNumber = new Button(g1, SWT.RADIO); + setHPagesNumber.setText(SDMessages._110); + setHPagesNumber.setLayoutData(newGridData(1)); + setHPagesNumber.addSelectionListener(selectionListener); + + hPagesNum = new Text(g1, SWT.SINGLE | SWT.BORDER); + hPagesNum.addModifyListener(modifyListener); + + setVPagesNumber = new Button(g1, SWT.RADIO); + setVPagesNumber.setText(SDMessages._111); + setVPagesNumber.setLayoutData(newGridData(1)); + setVPagesNumber.addSelectionListener(selectionListener); + + vPagesNum = new Text(g1, SWT.SINGLE | SWT.BORDER); + vPagesNum.addModifyListener(modifyListener); + + Label nbTotal = new Label(g1, SWT.SHADOW_NONE | SWT.RIGHT); + nbTotal.setText(SDMessages._109); + // nbTotal.setLayoutData(newGridData(1)); + + totalPages = new Text(g1, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY); + // nbHV.addModifyListener(modifListener); + + Group g2 = new Group(parent, SWT.SHADOW_NONE); + g2.setText(SDMessages._119); + GridData data = new GridData(GridData.GRAB_VERTICAL | GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL); + data.horizontalSpan = 3; + data.verticalSpan = 2; + g2.setLayoutData(data); + GridLayout g2layout = new GridLayout(); + // g2layout. + g2layout.numColumns = 1; + // SVLayout g2layout = new SVLayout(); + g2.setLayout(g2layout); + + GridData data2 = new GridData(GridData.GRAB_VERTICAL | GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL); + data2.horizontalSpan = 1; + data2.verticalSpan = 1; + + overviewCanvas = new LocalSD(g2, SWT.NO_BACKGROUND); + GridData seqDiagLayoutData = new GridData(GridData.HORIZONTAL_ALIGN_FILL// |GridData.GRAB_HORIZONTAL| + /* GridData.GRAB_VERTICAL| */| GridData.VERTICAL_ALIGN_FILL); + // seqDiagLayoutData.widthHint=400; + // seqDiagLayoutData.horizontalAlignment=GridData.HORIZONTAL_ALIGN_FILL; + overviewCanvas.setLayoutData(seqDiagLayoutData); + // overviewCanvas.resizeContents(100,100); + if (view.getContentsWidth() < view.getVisibleWidth() && view.getContentsHeight() < view.getVisibleHeight()) + test = 3; + else + test = 10; + overviewCanvas.setFrame(view.getFrame(), true); + overviewCanvas.zoomValue = (float) 1 / test; + overviewCanvas.setCornerControl(null); + seqDiagLayoutData.widthHint = overviewCanvas.getContentsWidth() / test; + seqDiagLayoutData.widthHint = overviewCanvas.getFrame().getWidth() / test + 15; + + if (view.getVisibleWidth() < view.getContentsWidth()) { + seqDiagLayoutData.widthHint = overviewCanvas.getContentsWidth() / test; + if (seqDiagLayoutData.widthHint > Display.getDefault().getClientArea().width / 4) + seqDiagLayoutData.widthHint = Display.getDefault().getClientArea().width / 4; + } else + seqDiagLayoutData.widthHint = overviewCanvas.getFrame().getWidth() / test + 15; + + if (view.getVisibleHeight() < view.getContentsHeight()) { + seqDiagLayoutData.heightHint = overviewCanvas.getContentsHeight() / test; + if (seqDiagLayoutData.heightHint > Display.getDefault().getClientArea().width / 4) + seqDiagLayoutData.heightHint = Display.getDefault().getClientArea().width / 4; + } else + seqDiagLayoutData.heightHint = overviewCanvas.getFrame().getHeight() / test; + + overviewCanvas.setEnabled(false); + + currentSelection = new Label(g2, SWT.SHADOW_NONE | SWT.LEFT); + currentSelection.setLayoutData(newGridData(1)); + + Group g3 = new Group(parent, SWT.SHADOW_NONE); + g3.setText(SDMessages._118); + g3.setLayoutData(newGridData(3)); + GridLayout g3layout = new GridLayout(); + g3layout.numColumns = 4; + g3.setLayout(g3layout); + + allPages = new Button(g3, SWT.RADIO); + allPages.setText(SDMessages._108); + allPages.setLayoutData(newGridData(4)); + allPages.addSelectionListener(selectionListener); + + currentPage = new Button(g3, SWT.RADIO); + currentPage.setText(SDMessages._107); + currentPage.setLayoutData(newGridData(4)); + currentPage.setEnabled(true); + currentPage.setSelection(true); + currentPage.addSelectionListener(selectionListener); + + pageList = new Button(g3, SWT.RADIO); + pageList.setText(SDMessages._106); + pageList.setLayoutData(newGridData(4)); + pageList.addSelectionListener(selectionListener); + + pageRange = new Button(g3, SWT.RADIO); + pageRange.setText(SDMessages._103); + pageRange.setLayoutData(newGridData(1)); + pageRange.addSelectionListener(selectionListener); + + fromPage = new Text(g3, SWT.SINGLE | SWT.BORDER); + + Label labelTo = new Label(g3, SWT.CENTER); + labelTo.setText(SDMessages._105); + + toPage = new Text(g3, SWT.SINGLE | SWT.BORDER); + + toolTip = new DiagramToolTip(overviewCanvas); + + overviewCanvas.getViewControl().addMouseTrackListener(new MouseTrackListener() { + + @Override + public void mouseEnter(MouseEvent e) { + toolTip.hideToolTip(); + } + + @Override + public void mouseExit(MouseEvent e) { + toolTip.hideToolTip(); + } + + @Override + public void mouseHover(MouseEvent e) { + int x1 = (int) (overviewCanvas.viewToContentsX(e.x) / overviewCanvas.zoomValue / stepX); + int x2 = (int) (overviewCanvas.viewToContentsY(e.y) / overviewCanvas.zoomValue / stepY); + int num = x1 + x2 * getNbRow() + 1; + if (num > maxNumOfPages()) + return; + if (num > 0) { + toolTip.showToolTip(String.valueOf(num)); + displayPageNum(); + } else { + currentSelection.setText("");//$NON-NLS-1$ + toolTip.hideToolTip(); + } + } + + }); + + overviewCanvas.addTraverseListener(new TraverseListener() { + + @Override + public void keyTraversed(TraverseEvent e) { + if ((e.detail == SWT.TRAVERSE_TAB_NEXT) || (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)) + e.doit = true; + } + + }); + + overviewCanvas.addFocusListener(new FocusListener() { + + @Override + public void focusGained(FocusEvent e) { + overviewCanvas.redraw(); + } + + @Override + public void focusLost(FocusEvent e) { + overviewCanvas.redraw(); + } + }); + + if (showPrintButton) { + Composite printerDlg = new Composite(parent, SWT.NONE); + data = GridUtil.createHorizontalFill(); + data.horizontalSpan = 6; + parentLayout = new GridLayout(); + parentLayout.numColumns = 2; + printerDlg.setLayout(parentLayout); + printerDlg.setLayoutData(data); + + Label label = new Label(printerDlg, SWT.NONE); + label.setLayoutData(GridUtil.createHorizontalFill()); + printerDialog = new Button(printerDlg, SWT.PUSH); + printerDialog.setText(SDMessages._115); + + printerDialog.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + + printButtonSelected(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + + }); + } + + updatePrinterStatus(); + + return parent; + } + + public int getPagesForSelection() { + return nbPages; + } + + public boolean okPressed() { + printAll = allPages.getSelection(); + printCurrent = currentPage.getSelection(); + printSelection = pageList.getSelection(); + printRange = pageRange.getSelection(); + try { + if (printRange) { + from = Integer.valueOf(fromPage.getText()).intValue(); + to = Integer.valueOf(toPage.getText()).intValue(); + if (from > maxNumOfPages() || to > maxNumOfPages() || from <= 0 || to <= 0) { + MessageDialog.openError(getShell(), SDMessages._98, SDMessages._99); + return false; + } + } else if (setHPagesNumber.getSelection() && nbPages <= 0) { + MessageDialog.openError(getShell(), SDMessages._98, SDMessages._101); + return false; + } else if (setVPagesNumber.getSelection() && nbPages <= 0) { + MessageDialog.openError(getShell(), SDMessages._98, SDMessages._100); + return false; + } else if (printSelection && getPageList().length <= 0) { + MessageDialog.openError(getShell(), SDMessages._98, SDMessages._102); + return false; + } + + } catch (Exception e) { + MessageDialog.openError(getShell(), SDMessages._98, SDMessages._99); + from = 0; + to = 0; + return false; + } + + return true; + } + + public ImageData drawRegionSelected(Image img, Rectangle r, RGB color) { + ImageData id = img.getImageData(); + for (int a = 0; a < r.width && r.x + a < id.width; a++) { + for (int b = 0; b < r.height && r.y + b < id.height; b++) { + int index = id.getPixel(r.x + a, r.y + b); + RGB rgb = id.palette.getRGB(index); + rgb = combine(color, rgb); + id.setPixel(r.x + a, r.y + b, id.palette.getPixel(rgb)); + } + } + return id; + } + + public static RGB combine(RGB front, RGB back) { + int _af = 128; + if (_af == 1) + return front; + if (_af == 0) + return back; + int _ab = 200; + if (_ab == 0) + return front; + + double af = (_af) / 255.0; + double rf = front.red; + double gf = front.green; + double bf = front.blue; + + double ab = (_ab) / 255.0; + double rb = back.red; + double gb = back.green; + double bb = back.blue; + + double k = (1.0 - af) * ab; + int r = (int) ((af * rf + k * rb)); + int g = (int) ((af * gf + k * gb)); + int b = (int) ((af * bf + k * bb)); + + return new RGB(r, g, b); + } + + protected void computeStepXY() { + float cw = overviewCanvas.getContentsWidth() / overviewCanvas.zoomValue; + float ch = overviewCanvas.getContentsHeight() / overviewCanvas.zoomValue; + try { + if (printerData == null) { + stepX = 0; + stepY = 0; + nbPages = 0; + zoomFactor = 0; + } else { + Printer printer = new Printer(printerData); + if (setHPagesNumber.getSelection()) { + nbPages = Integer.valueOf(hPagesNum.getText()).intValue(); + float z1 = (float) view.getContentsWidth() / (cw); + float z2 = printer.getClientArea().width / ((float) view.getContentsWidth() / nbPages); + + stepY = printer.getClientArea().height / z1 / z2; + stepX = cw / nbPages; + } else if (setVPagesNumber.getSelection()) { + nbPages = Integer.valueOf(vPagesNum.getText()).intValue(); + float z1 = (float) view.getContentsHeight() / (ch); + float z2 = printer.getClientArea().height / ((float) view.getContentsHeight() / nbPages); + stepX = printer.getClientArea().width / z1 / z2; + stepY = ch / nbPages; + } else { + float z1 = view.getContentsWidth() / (cw); + stepX = ((float) view.getVisibleWidth() / z1); + nbPages = Math.round(cw / stepX); + if (nbPages == 0) + nbPages = 1; + int pw = printer.getClientArea().width; + int ph = printer.getClientArea().height; + float z2 = pw / ((float) view.getContentsWidth() / nbPages); + stepY = ((float) ph / z1 / z2); + } + } + } finally { + stepX = stepY = nbPages = 0; + zoomFactor = 0; + } + sTX = stepX * (view.getContentsWidth() / cw); + sTY = stepY * (view.getContentsHeight() / ch); + float rat = 1; + if ((view.getVisibleWidth() > view.getContentsWidth()) && (setVPagesNumber.getSelection() || setHPagesNumber.getSelection())) + rat = (float) view.getVisibleWidth() / (float) view.getContentsWidth(); + zoomFactor = (overviewCanvas.getContentsWidth() / cw) / overviewCanvas.getZoomFactor() * rat;// /view.getZoomFactor(); + } + + public int[] getPageList() { + return pagesList; + } + + public void addToPagesList(int num) { + int temp[] = new int[pagesList.length + 1]; + System.arraycopy(pagesList, 0, temp, 0, pagesList.length); + temp[temp.length - 1] = num; + pagesList = new int[temp.length]; + System.arraycopy(temp, 0, pagesList, 0, temp.length); + } + + public void removeFromPagesList(int num) { + int pos = Arrays.binarySearch(pagesList, num); + int temp[] = new int[pagesList.length - 1]; + System.arraycopy(pagesList, 0, temp, 0, pos); + System.arraycopy(pagesList, pos + 1, temp, pos, pagesList.length - pos - 1); + pagesList = new int[temp.length]; + System.arraycopy(temp, 0, pagesList, 0, temp.length); + } + + public int maxNumOfPages() { + int max = getNbRow() * getNbLines(); + return max; + } + + public int getNbRow() { + if (!setHPagesNumber.isDisposed()) { + int cw = (int) (overviewCanvas.getContentsWidth() / overviewCanvas.zoomValue); + if (stepX == 0) + return 1; + int row = (int) (cw / stepX); + if (setHPagesNumber.getSelection()) + row = Math.round((float) cw / stepX); + else if ((cw % stepX != 0)) + row++; + + nbRows = row; + } + return nbRows; + } + + public int getNbLines() { + if (!setVPagesNumber.isDisposed()) { + int ch = (int) (overviewCanvas.getContentsHeight() / overviewCanvas.zoomValue); + if (stepY == 0) + return 1; + int line = (int) (ch / stepY); + if (setVPagesNumber.getSelection()) + line = Math.round((float) ch / stepY); + else if (ch % stepY != 0) + line++; + nbLines = line; + } + return nbLines; + } + + public boolean printAll() { + return printAll; + } + + public boolean printCurrent() { + return printCurrent; + } + + public boolean printSelection() { + return printSelection; + } + + public boolean printRange() { + return printRange; + } + + public float getStepX() { + return sTX; + } + + public float getStepY() { + return sTY; + } + + public float getZoomFactor() { + return zoomFactor; + } + + // public abstract void createButtonsForButtonBar(Composite parent); + + public PrinterData getPrinterData() { + return printerData; + } + + public int getFrom() { + return from; + } + + public int getTo() { + return to; + } + + protected void displayPageNum() { + if (pageNum > 0) { + String message = MessageFormat.format(SDMessages._117, new Object[] { Integer.valueOf(pageNum) }); + currentSelection.setText(message); + currentSelection.getParent().layout(); + } + } + + public Shell getShell() { + return shell; + } + + public void setShell(Shell shell) { + this.shell = shell; + } + + public void printButtonSelected() { + PrintDialog printer = new PrintDialog(getShell()); + if (allPages.getSelection()) + printer.setScope(PrinterData.ALL_PAGES); + if (currentPage.getSelection()) + printer.setScope(PrinterData.SELECTION); + if (pageList.getSelection()) + printer.setScope(PrinterData.SELECTION); + if (pageRange.getSelection()) { + printer.setScope(PrinterData.PAGE_RANGE); + from = Integer.valueOf(fromPage.getText()).intValue(); + to = Integer.valueOf(toPage.getText()).intValue(); + printer.setStartPage(from); + printer.setEndPage(to); + } + + PrinterData newPrinterData = printer.open(); + if (newPrinterData != null) { + printerData = newPrinterData; + } + updatePrinterStatus(); + + if (printer.getScope() == PrinterData.ALL_PAGES) { + allPages.setSelection(true); + currentPage.setSelection(false); + pageList.setSelection(false); + pageRange.setSelection(false); + hPagesNum.setEnabled(false); + vPagesNum.setEnabled(false); + } + if (printer.getScope() == PrinterData.PAGE_RANGE) { + allPages.setSelection(false); + currentPage.setSelection(false); + pageList.setSelection(false); + pageRange.setSelection(true); + fromPage.setEnabled(true); + toPage.setEnabled(true); + fromPage.setText((Integer.valueOf(printer.getStartPage())).toString()); + toPage.setText((Integer.valueOf(printer.getEndPage())).toString()); + } + computeStepXY(); + overviewCanvas.redraw(); + } + + public void setParentWizardPage(WizardPage parent) { + parentWizardPage = parent; + } + + public void setParentDialog(SDPrintDialog parent) { + parentDialog = parent; + } + + protected void updatePrinterStatus() { + if (parentWizardPage != null) { + // used in the wizard dialog + if (printerData == null) { + // show error message and disable Finish button + parentWizardPage.setErrorMessage(SDMessages._135); + parentWizardPage.setPageComplete(false); + } else { + // clear error message and enable Finish button + parentWizardPage.setErrorMessage(null); + parentWizardPage.setPageComplete(true); + } + } else if (parentDialog != null) { + // used in the print dialog + if (printerData == null) { + // show error message and disable OK button + parentDialog.setErrorMessage(SDMessages._135); + parentDialog.setPageComplete(false); + } else { + // clear error message and enable OK button + parentDialog.setErrorMessage(null); + parentDialog.setPageComplete(true); + } + } + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SortAsyncForBackward.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SortAsyncForBackward.java new file mode 100755 index 0000000000..e70abc6a9c --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SortAsyncForBackward.java @@ -0,0 +1,87 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: SortAsyncForBackward.java,v 1.2 2006/09/20 20:56:27 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.util; + +import java.io.Serializable; +import java.util.Comparator; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.AsyncMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; + +/** + * Asynchronous message comparator + * + * Compares two asyncMessages only taking into account the event occurrence when their + * appear.
+ * + * Used to order the AsyncMessage list insuring that the previous node has both of his ends smaller than the current node + * + * @author sveyrier + * + */ +public class SortAsyncForBackward implements Comparator, Serializable { + + /** + * Serial version UID + */ + private static final long serialVersionUID = 603959931263853359L; + + /** + * Compares two asynchronous messages Returns 0 (equal) if one of the message is not asynchronous + * + * @return 1 if arg0 is greater, 0 if equal, -1 otherwise + */ + @Override + public int compare(GraphNode arg0, GraphNode arg1) { + if (arg0 instanceof AsyncMessage && arg1 instanceof AsyncMessage) { + AsyncMessage m1 = (AsyncMessage) arg0; + AsyncMessage m2 = (AsyncMessage) arg1; + int m1Max, m2Max; + // AsyncMessage has two ends which may have different event occurrences + // Search for the greater event occurrence for each messages + if (m1.getStartOccurrence() > m1.getEndOccurrence()) + m1Max = m1.getStartOccurrence(); + else + m1Max = m1.getEndOccurrence(); + if (m2.getStartOccurrence() > m2.getEndOccurrence()) + m2Max = m2.getStartOccurrence(); + else + m2Max = m2.getEndOccurrence(); + + int m1Min, m2Min; + // Search for the smaller event occurrence for each messages + if (m1.getStartOccurrence() > m1.getEndOccurrence()) + m1Min = m1.getEndOccurrence(); + else + m1Min = m1.getStartOccurrence(); + if (m2.getStartOccurrence() > m2.getEndOccurrence()) + m2Min = m2.getEndOccurrence(); + else + m2Min = m2.getStartOccurrence(); + + if (m1Max > m2Max) + return 1; + else if (m1Max == m2Max) + if (m1Min == m2Min) + return 0; + else if (m1Min > m2Min) + return -1; + else + return 1; + else + return -1; + } else + return 0; + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SortAsyncMessageComparator.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SortAsyncMessageComparator.java new file mode 100755 index 0000000000..259e014cff --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SortAsyncMessageComparator.java @@ -0,0 +1,85 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: SortAsyncMessageComparator.java,v 1.2 2006/09/20 20:56:27 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.util; + +import java.io.Serializable; +import java.util.Comparator; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.AsyncMessage; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; + +/** + * Asynchronous message comparator Compare two AsyncMessages only taking into account the event occurrence when their + * appear.
+ * + * Used to order the AsyncMessage list insuring that next node has one of his ends greater than the current node + * + * @author sveyrier + * + */ +public class SortAsyncMessageComparator implements Comparator, Serializable { + + /** + * Serial version UID + */ + private static final long serialVersionUID = 1L; + + /** + * Compares two synchronous syncMessages Returns 0 (equal) if one of the message is not synchronous + * + * @return 1 if arg0 is greater, 0 if equal, -1 otherwise + */ + @Override + public int compare(GraphNode arg0, GraphNode arg1) { + if (arg0 instanceof AsyncMessage && arg1 instanceof AsyncMessage) { + AsyncMessage m1 = (AsyncMessage) arg0; + AsyncMessage m2 = (AsyncMessage) arg1; + int m1Min, m2Min; + // AsyncMessage has two ends which may have different event occurrences + // Search for the smaller event occurrence for each messages + if (m1.getStartOccurrence() > m1.getEndOccurrence()) + m1Min = m1.getEndOccurrence(); + else + m1Min = m1.getStartOccurrence(); + if (m2.getStartOccurrence() > m2.getEndOccurrence()) + m2Min = m2.getEndOccurrence(); + else + m2Min = m2.getStartOccurrence(); + + int m1Max, m2Max; + // Search for the greater event occurrence for each messages + if (m1.getStartOccurrence() > m1.getEndOccurrence()) + m1Max = m1.getStartOccurrence(); + else + m1Max = m1.getEndOccurrence(); + if (m2.getStartOccurrence() > m2.getEndOccurrence()) + m2Max = m2.getStartOccurrence(); + else + m2Max = m2.getEndOccurrence(); + + if (m1Min > m2Min) + return 1; + else if ((m1Min == m2Min)) + if (m1Max == m2Max) + return 0; + else if (m1Max > m2Max) + return -1; + else + return 1; + else + return -1; + } else + return 0; + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SortSyncMessageComparator.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SortSyncMessageComparator.java new file mode 100755 index 0000000000..852e7d98a2 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/SortSyncMessageComparator.java @@ -0,0 +1,57 @@ +/********************************************************************** + * Copyright (c) 2005, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: SortSyncMessageComparator.java,v 1.2 2006/09/20 20:56:27 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.util; + +import java.io.Serializable; +import java.util.Comparator; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.GraphNode; +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SyncMessage; + +/** + * Synchronous message comparator Compare two syncMessages only taking into account the event occurrence when their + * appear.
+ * + * The message with the greater event occurrence is considered to be the greater.
+ * + * @author sveyrier + * + */ +public class SortSyncMessageComparator implements Comparator, Serializable { + + /** + * Serial version UID + */ + private static final long serialVersionUID = 4781250984753283718L; + + /** + * Compares two synchronous syncMessages. Returns 0 (equal) if one of the message is not synchronous + * + * @return 1 if arg0 is greater, 0 if equal, -1 otherwise + */ + @Override + public int compare(GraphNode arg0, GraphNode arg1) { + if (arg0 instanceof SyncMessage && arg1 instanceof SyncMessage) { + SyncMessage m1 = (SyncMessage) arg0; + SyncMessage m2 = (SyncMessage) arg1; + if (m1.getEventOccurrence() > m2.getEventOccurrence()) + return 1; + else if (m1.getEventOccurrence() == m2.getEventOccurrence()) + return 0; + else + return -1; + } else + return 0; + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/TimeEventComparator.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/TimeEventComparator.java new file mode 100755 index 0000000000..58247eac6f --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/TimeEventComparator.java @@ -0,0 +1,50 @@ +/********************************************************************** + * Copyright (c) 2005, 2006, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * $Id: TimeEventComparator.java,v 1.2 2006/09/20 20:56:27 ewchan Exp $ + * + * Contributors: + * IBM - Initial API and implementation + * Bernd Hufmann - Updated for TMF + **********************************************************************/ +package org.eclipse.linuxtools.tmf.ui.views.uml2sd.util; + +import java.io.Serializable; +import java.util.Comparator; + +import org.eclipse.linuxtools.tmf.ui.views.uml2sd.core.SDTimeEvent; + +/** + * Time event comparator + * + * @author sveyrier + * + */ +public class TimeEventComparator implements Comparator, Serializable { + + /** + * Serial version UID + */ + private static final long serialVersionUID = 5885497718872575669L; + + /** + * Compares two time events. + * + * @return 1 if arg0 is greater, 0 if equal, -1 otherwise + */ + @Override + public int compare(SDTimeEvent arg0, SDTimeEvent arg1) { + SDTimeEvent t1 = (SDTimeEvent) arg0; + SDTimeEvent t2 = (SDTimeEvent) arg1; + if (t1.getEvent() > t2.getEvent()) + return 1; + else if (t1.getEvent() == t2.getEvent()) + return 0; + else + return -1; + } + +} diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/messages.properties b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/messages.properties new file mode 100755 index 0000000000..d427f461b2 --- /dev/null +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/views/uml2sd/util/messages.properties @@ -0,0 +1,177 @@ +############################################################################### +# Copyright (c) 2005, 2008, 2011 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# $Id: messages.properties,v 1.9 2008/05/14 19:40:36 jkubasta Exp $ +# +# Contributors: +# IBM Corporation - initial API and implementation +# Bernd Hufmann - Updated for TMF +############################################################################### + +# NLS_MESSAGEFORMAT_VAR +# NLS_ENCODING=UTF-8 + +##SD +_1 = Lifeline node {0} +_2 = Message node {0} from Lifeline {1} at event occurrence {2} to Lifeline {3} at event occurrence {4} +_3 = Lost Message node {0} from Lifeline {1} at event occurrence {2} +_4 = Found Message node {0} to Lifeline {1} at event occurrence {2} +_5 = Execution occurrence {0} on Lifeline {1} from event occurrence {2} to {3} + +# Full words versions are for accessibility (screen reader) +_6 = {0,number} day {1,number,00} h +_7 = {0,number} day {1,number,00} hours +_8 = {0,number} days {1,number,00} h +_9 = {0,number} days {1,number,00} hours +_10 = {0,number} h {1,number,00} min +_11 = {0,number} hours {1,number,00} minutes +_12 = {0,number} min {1,number,00} s +_13 = {0,number} minutes {1,number,00} seconds +_14 = {0,number,0.###} s +_15 = {0,number,0.###} seconds +_16 = {0,number,0.###} ms +_17 = {0,number,0.###} milliseconds + +# \u00B5 is the greek mu letter for micro(second) +_18 = {0,number,0.###} \u00B5s +_19 = {0,number,0.###} microseconds +_20 = Time elapsed from selection {0} +_21 = Find +_22 = Close +_23 = String not found +_24 = Sequence Diagram Find +_25 = Search for +_26 = Matching string (regular expression): +_27 = Case sensitive +_28 = Lifeline +_29 = Stop +_30 = Synchronous message +_31 = Synchronous message return +_32 = Asynchronous message +_33 = Asynchronous message return +_34 = or +_35 = Previous page +_36 = Next page +_37 = Go to previous page +_38 = Go to next page +_39 = Go to message +_40 = Go to message return +_41 = Find... +_42 = Edit filters... +#_43 = Hide/Display Patterns... +_43 = Hide Patterns... +_44 = Pages... +_45 = Hide internal messages +_46 = Hide or display internal messages +_47 = Zoom in +_48 = Zoom in the diagram +_49 = Reset zoom factor +_50 = Reset zoom factor +_51 = Zoom out +_52 = Zoom out the diagram +_53 = Select +_54 = Select +_55 = Max +_56 = Min +_57 = List of hide/display patterns +_58 = hide +_59 = display +_60 = Edit it +_61 = Add... +_62 = Create +_63 = Update +_64 = Remove +#_65 = Sequence Diagram Hide/Display Patterns +_65 = Sequence Diagram Hide Patterns +_66 = Definition of Hide Pattern +_67 = Page navigation +_68 = Sequence Diagram Pages +_69 = {0,number} <= value <= {1,number} +_70 = Total: +_71 = pages +_72 = page +_73 = pages +_74 = Currently in all pages: +_75 = Current page: +_76 = Maximum number of {0} in a page: +_77 = Navigation +_78 = Click to open Overview\nDrag mouse to scroll\nUse Shift+Drag, Ctrl+Drag to scroll slower\nUse Shift+Ctrl+Drag to scroll very fine +_79 = "Use 'Open with...|UML2 interactions' menu item to populate this view" +_80 = &Lifeline Width (in pixels) +_81 = AaBbYyZz +_82 = &Increase font size when zooming +_83 = &Exclude external time +_84 = &Use gradient color +_85 = Background +_86 = Lines +_87 = Text +_88 = Lifeline +_89 = Execution Occurrence +_90 = Sync Message +_91 = Sync Message Return +_92 = Async Message +_93 = Async Message Return +_94 = Frame +_95 = Lifeline Header +_96 = Frame Title +_97 = &Show tooltips +_98 = Error +_99 = Invalid range +_100 = Number of vertical pages is invalid +_101 = Number of horizontal pages is invalid +_102 = There is no page selected +_103 = F&rom page +_104 = From +_105 = &to +_106 = Selected pages +_107 = Current view +_108 = &All pages +_109 = Total number of pages: +_110 = Number of horizontal pages: +_111 = Number of vertical pages: +_112 = Use current zoom +_113 = Zoom options +_114 = Print +_115 = Printer... +_116 = {0} + {1} +_117 = Page {0} +_118 = Print range +_119 = Preview +#report messages +_120 = Print +_121 = Define printer options for printing the Sequence Diagram +_122=Print Sequence Diagram content + +_123 = TimeCompression bar configuration +_124 = Min time +_125 = Max time +_126 = Default +_127 = {0,number,0.###} +_128 = days +_129 = hours +_130 = minutes +_131 = s +_132 = ms +# \u00B5 is the greek mu letter for micro(second) +_133 = \u00B5s + +_134 = Min Time {0} \n\nCurrent Time {1} \n\nMaxTime {2} + +_135 = No default printer is selected. Click Printer button first and select a printer. + +_136 = Scale +_137 = Precision +_138 = Delta + +_139 = First page +_140 = Go to first page + +_141 = Last page +_142 = Go to last page + +uml_23 = Show the node end +uml_25 = Show the node start +uml_45 = Configure Min Max diff --git a/org.eclipse.linuxtools.tmf/META-INF/MANIFEST.MF b/org.eclipse.linuxtools.tmf/META-INF/MANIFEST.MF index 6dddb6b6db..362369a802 100644 --- a/org.eclipse.linuxtools.tmf/META-INF/MANIFEST.MF +++ b/org.eclipse.linuxtools.tmf/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.linuxtools.tmf -Bundle-Version: 0.3.1.qualifier +Bundle-Version: 0.3.2.qualifier Bundle-Activator: org.eclipse.linuxtools.tmf.TmfCorePlugin Bundle-Vendor: %Bundle-Vendor Require-Bundle: org.eclipse.core.runtime, @@ -20,5 +20,6 @@ Export-Package: org.eclipse.linuxtools.tmf, org.eclipse.linuxtools.tmf.parser, org.eclipse.linuxtools.tmf.request, org.eclipse.linuxtools.tmf.signal, - org.eclipse.linuxtools.tmf.trace + org.eclipse.linuxtools.tmf.trace, + org.eclipse.linuxtools.tmf.uml2sd Bundle-Localization: plugin diff --git a/org.eclipse.linuxtools.tmf/pom.xml b/org.eclipse.linuxtools.tmf/pom.xml index eee62c430a..a4cb0c1d25 100644 --- a/org.eclipse.linuxtools.tmf/pom.xml +++ b/org.eclipse.linuxtools.tmf/pom.xml @@ -18,7 +18,7 @@ org.eclipse.linuxtools.tmf - 0.3.1-SNAPSHOT + 0.3.2-SNAPSHOT eclipse-plugin Linux Tools TMF (LTTng) Plug-in diff --git a/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/component/TmfComponent.java b/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/component/TmfComponent.java index 7cc507a3ef..a3626e77be 100644 --- a/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/component/TmfComponent.java +++ b/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/component/TmfComponent.java @@ -30,6 +30,10 @@ public abstract class TmfComponent implements ITmfComponent { // Constructor // ------------------------------------------------------------------------ + public TmfComponent() { + this(""); //$NON-NLS-1$ + } + public TmfComponent(String name) { fName = name; // if (Tracer.isComponentTraced()) Tracer.traceComponent(this, "created"); diff --git a/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/event/TmfTimestamp.java b/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/event/TmfTimestamp.java index b786e3dd30..63168db9af 100644 --- a/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/event/TmfTimestamp.java +++ b/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/event/TmfTimestamp.java @@ -256,6 +256,23 @@ public class TmfTimestamp implements Cloneable { return ts2.fValue - ts1.fValue; } + /** + * Compute the delta between two timestamps (adjusted to scale of current timestamp). + * + * @param reference the reference timestamp to synchronize with + * @return the delta timestamp + * @throws ArithmeticException + */ + public TmfTimestamp getDelta(TmfTimestamp other) throws ArithmeticException { + TmfTimestamp newSecond = other; + if ((fScale != other.fScale) || (fPrecision != other.fPrecision)) { + newSecond = other.synchronize(0, fScale); + } + return new TmfTimestamp(fValue - newSecond.fValue, + fScale, + newSecond.fPrecision > fPrecision ? newSecond.fPrecision : fPrecision); + } + /** * Compare with another timestamp * diff --git a/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/ITmfAsyncSequenceDiagramEvent.java b/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/ITmfAsyncSequenceDiagramEvent.java new file mode 100644 index 0000000000..b3ba7cc14a --- /dev/null +++ b/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/ITmfAsyncSequenceDiagramEvent.java @@ -0,0 +1,21 @@ +/********************************************************************** + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + **********************************************************************/ +package org.eclipse.linuxtools.tmf.uml2sd; + +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; + +public interface ITmfAsyncSequenceDiagramEvent extends ITmfSyncSequenceDiagramEvent { + /** + * @return End timestamp of message (i.e. receive time) + */ + public TmfTimestamp getEndTime(); +} diff --git a/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/ITmfSyncSequenceDiagramEvent.java b/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/ITmfSyncSequenceDiagramEvent.java new file mode 100644 index 0000000000..b0db304b5e --- /dev/null +++ b/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/ITmfSyncSequenceDiagramEvent.java @@ -0,0 +1,37 @@ +/********************************************************************** + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + **********************************************************************/ +package org.eclipse.linuxtools.tmf.uml2sd; + +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; + +public interface ITmfSyncSequenceDiagramEvent { + + /** + * @return Name of message + */ + public String getName(); + + /** + * @return name of sender of message + */ + public String getSender(); + + /** + * @return Name of receiver of message + */ + public String getReceiver(); + + /** + * @return Start timestamp of message (i.e. send time) + */ + public TmfTimestamp getStartTime(); +} diff --git a/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/TmfAsyncSequenceDiagramEvent.java b/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/TmfAsyncSequenceDiagramEvent.java new file mode 100644 index 0000000000..422723078f --- /dev/null +++ b/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/TmfAsyncSequenceDiagramEvent.java @@ -0,0 +1,54 @@ +/********************************************************************** + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + **********************************************************************/ +package org.eclipse.linuxtools.tmf.uml2sd; + +import org.eclipse.linuxtools.tmf.event.TmfEvent; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; + +/** + * TmfAsyncSequenceDiagramEvent + *

+ * Sample implementation of the ITmfAsyncSequenceDiagramEvent + *

+ */ +public class TmfAsyncSequenceDiagramEvent extends TmfSyncSequenceDiagramEvent implements ITmfAsyncSequenceDiagramEvent { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + final protected TmfTimestamp fEndTime; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public TmfAsyncSequenceDiagramEvent(TmfEvent startEvent, TmfEvent endEvent, String sender, String receiver, String name) { + super(startEvent, sender, receiver, name); + + if (endEvent == null) { + throw new IllegalArgumentException("TmfAsyncSequenceDiagramEvent constructor: endEvent=null"); //$NON-NLS-1$ + } + fEndTime = endEvent.getTimestamp().clone(); + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.uml2sd.ITmfAsyncSequenceDiagramEvent#getEndTime() + */ + @Override + public TmfTimestamp getEndTime() { + return fEndTime; + } +} diff --git a/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/TmfSyncSequenceDiagramEvent.java b/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/TmfSyncSequenceDiagramEvent.java new file mode 100644 index 0000000000..2b7375decc --- /dev/null +++ b/org.eclipse.linuxtools.tmf/src/org/eclipse/linuxtools/tmf/uml2sd/TmfSyncSequenceDiagramEvent.java @@ -0,0 +1,92 @@ +/********************************************************************** + * Copyright (c) 2011 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Bernd Hufmann - Initial API and implementation + **********************************************************************/ +package org.eclipse.linuxtools.tmf.uml2sd; + +import org.eclipse.linuxtools.tmf.event.TmfEvent; +import org.eclipse.linuxtools.tmf.event.TmfTimestamp; + +/** + * TmfSyncSequenceDiagramEvent + *

+ * Sample implementation of the ITmfSyncSequenceDiagramEvent + *

+ */ +public class TmfSyncSequenceDiagramEvent implements ITmfSyncSequenceDiagramEvent { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + final protected TmfTimestamp fStartTime; + final protected String fSender; + final protected String fReceiver; + final protected String fName; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + public TmfSyncSequenceDiagramEvent(TmfEvent startEvent, String sender, String receiver, String name) { + + if (startEvent == null || sender == null || receiver == null || name == null) { + throw new IllegalArgumentException("TmfSyncSequenceDiagramEvent constructor: " + //$NON-NLS-1$ + (startEvent == null ? ", startEvent=null" : "") + //$NON-NLS-1$ //$NON-NLS-2$ + (sender == null ? ", sender=null" : "") + //$NON-NLS-1$ //$NON-NLS-2$ + (receiver == null ? ", receiver=null" : "") + //$NON-NLS-1$ //$NON-NLS-2$ + (name == null ? ", name=null" : "")); //$NON-NLS-1$ //$NON-NLS-2$ + } + + fStartTime = startEvent.getTimestamp().clone(); + + fSender = sender; + fReceiver = receiver; + + fName = name; + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.uml2sd.ITmfSyncSequenceDiagramEvent#getSender() + */ + @Override + public String getSender() { + return fSender; + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.uml2sd.ITmfSyncSequenceDiagramEvent#getReceiver() + */ + @Override + public String getReceiver() { + return fReceiver; + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.uml2sd.ITmfSyncSequenceDiagramEvent#getName() + */ + @Override + public String getName() { + return fName; + } + + /* + * (non-Javadoc) + * @see org.eclipse.linuxtools.tmf.uml2sd.ITmfSyncSequenceDiagramEvent#getStartTime() + */ + @Override + public TmfTimestamp getStartTime() { + return fStartTime; + } +} -- 2.34.1