11package uk .ac .stfc .isis .ibex .e4 .ui .perspectiveswitcher ;
22
3+ import java .lang .reflect .Type ;
4+ import java .util .HashMap ;
5+ import java .util .List ;
6+ import java .util .Map ;
7+
8+ import org .apache .logging .log4j .Logger ;
39import org .eclipse .e4 .core .di .annotations .Execute ;
410import org .eclipse .e4 .core .services .events .IEventBroker ;
511import org .eclipse .e4 .ui .model .application .MApplication ;
1117import org .eclipse .e4 .ui .workbench .UIEvents .EventTags ;
1218import org .eclipse .e4 .ui .workbench .modeling .EModelService ;
1319import org .eclipse .e4 .ui .workbench .modeling .EPartService ;
20+ import org .eclipse .swt .widgets .Display ;
1421import org .osgi .service .event .Event ;
1522import org .osgi .service .event .EventHandler ;
1623
24+ import com .google .gson .Gson ;
25+ import com .google .gson .reflect .TypeToken ;
26+
1727import uk .ac .stfc .isis .ibex .e4 .ui .perspectiveswitcher .controls .ResetLayoutButtonModel ;
18- import uk .ac .stfc .isis .ibex .preferences .PreferenceSupplier ;
28+ import uk .ac .stfc .isis .ibex .epics .observing .BaseObserver ;
29+ import uk .ac .stfc .isis .ibex .epics .switching .ObservableFactory ;
30+ import uk .ac .stfc .isis .ibex .epics .switching .OnInstrumentSwitch ;
31+ import uk .ac .stfc .isis .ibex .epics .switching .SwitchableObservable ;
32+ import uk .ac .stfc .isis .ibex .instrument .InstrumentUtils ;
33+ import uk .ac .stfc .isis .ibex .instrument .channels .CompressedCharWaveformChannel ;
34+ import uk .ac .stfc .isis .ibex .logger .IsisLog ;
1935
2036/**
2137 * Copies all snippet perspectives to perspective stack called
2541 */
2642public class CopyPerspectiveSnippetProcessor {
2743
44+ private static final Logger LOG = IsisLog .getLogger (CopyPerspectiveSnippetProcessor .class );
45+
2846 private PerspectivesProvider perspectivesProvider ;
2947 private MPerspectiveStack perspectiveStack ;
48+ private SwitchableObservable <String > perspectiveSettings ;
49+
50+ private final PerspectivePreferenceSupplier preferenceSupplier = new PerspectivePreferenceSupplier ();
51+
52+ private static final Gson GSON = new Gson ();
53+ private static final Type SERVER_IOC_DATA_FORMAT = new TypeToken <Map <String , Boolean >>() { }.getType ();
54+
55+ private ObservableFactory switchingObsFactory = new ObservableFactory (OnInstrumentSwitch .SWITCH );
3056
57+ private static final String PERSPECTIVE_CONFIG_PV = "CS:PERSP:SETTINGS" ;
58+ private static final String PERSPECTIVE_CONFIG_PV_DISCONNECTED_MSG = "Remote perspective PV disconnected, using last known good value." ;
59+ private static final String PERSPECTIVE_CONFIG_PV_ERROR_MSG = "Remote perspective PV in error, using last known good value." ;
60+
61+ /* We are expecting the PV to take a while to connect after starting the client.
62+ * This is to prevent excessive logging on the client startup.
63+ */
64+ private final long connectionCheckTimeout = 10000 ;
65+ private boolean connectionCheck = false ;
66+
3167 /**
32- * Clone each snippet that is a perspective and add the cloned perspective
33- * to the main PerspectiveStack.
68+ * Clone each snippet that is a perspective and add the cloned perspective to
69+ * the main PerspectiveStack.
3470 *
35- * @param app
36- * The MApplication used.
37- * @param partService
38- * The EPartService used.
39- * @param modelService
40- * The EModelService used.
41- * @param broker
42- * The IEventBroker used.
71+ * @param app The MApplication used.
72+ * @param partService The EPartService used.
73+ * @param modelService The EModelService used.
74+ * @param broker The IEventBroker used.
4375 */
4476 @ Execute
45- public void execute (MApplication app , EPartService partService , EModelService modelService , IEventBroker broker ) {
77+ public void execute (MApplication app , EModelService modelService , EPartService partService , IEventBroker broker ) {
4678 perspectivesProvider = new PerspectivesProvider (app , partService , modelService );
4779 perspectiveStack = perspectivesProvider .getTopLevelStack ();
4880
81+ perspectiveSettings = switchingObsFactory .getSwitchableObservable (new CompressedCharWaveformChannel (),
82+ InstrumentUtils .addPrefix (PERSPECTIVE_CONFIG_PV ));
83+
4984 // Only do this when no other children, or the restored workspace state
5085 // will be overwritten.
5186 if (!perspectiveStack .getChildren ().isEmpty ()) {
@@ -56,36 +91,110 @@ public void execute(MApplication app, EPartService partService, EModelService mo
5691 // perspective into the main PerspectiveStack
5792 boolean isFirst = true ;
5893 for (MPerspective perspective : perspectivesProvider .getInitialPerspectives ()) {
59- if (!new PreferenceSupplier ().perspectivesToHide ().contains (perspective .getElementId ())) {
60- perspectiveStack .getChildren ().add (perspective );
61- if (isFirst ) {
62- perspectiveStack .setSelectedElement (perspective );
63- isFirst = false ;
64- }
94+
95+ perspectiveStack .getChildren ().add (perspective );
96+ if (isFirst ) {
97+ perspectiveStack .setSelectedElement (perspective );
98+ isFirst = false ;
6599 }
66100 subscribeChangedElement (broker , perspective );
67101 subscribeSelectedPerspective (broker , perspective );
68102 }
103+
104+ perspectiveSettings .subscribe (new BaseObserver <String >() {
105+ @ Override
106+ public void onValue (String value ) {
107+ if (!preferenceSupplier .getUseLocalPerspectives ()) {
108+ Map <String , Boolean > visiblePerspectives = GSON .fromJson (value , SERVER_IOC_DATA_FORMAT );
109+ setVisiblePerspectivesAsync (visiblePerspectives );
110+ }
111+ }
112+
113+ @ Override
114+ public void onError (Exception e ) {
115+ LOG .error (PERSPECTIVE_CONFIG_PV_DISCONNECTED_MSG );
116+ }
117+
118+ @ Override
119+ public void onConnectionStatus (boolean isConnected ) {
120+ if (!isConnected && connectionCheck ) {
121+ LOG .error (PERSPECTIVE_CONFIG_PV_ERROR_MSG );
122+ }
123+ }
124+ });
125+ setupConnectionCheckTimeout (connectionCheckTimeout );
126+
127+ preferenceSupplier .addUseLocalPerspectivesListener (useLocal -> {
128+ if (useLocal ) {
129+ setVisiblePerspectives (preferenceSupplier .perspectivesToHide ());
130+ } else {
131+ Map <String , Boolean > visiblePerspectives = GSON .fromJson (perspectiveSettings .getValue (), SERVER_IOC_DATA_FORMAT );
132+ setVisiblePerspectivesAsync (visiblePerspectives );
133+ }
134+ });
135+
136+ preferenceSupplier .addHiddenPerspectivesListener (hiddenPerspectives -> {
137+ if (preferenceSupplier .getUseLocalPerspectives ()) {
138+ setVisiblePerspectives (hiddenPerspectives );
139+ }
140+ });
141+
69142 ResetLayoutButtonModel .getInstance ().reset (perspectiveStack .getSelectedElement ());
70143 }
71144
145+ /**
146+ * Set which perspectives are visible based on a list of the ones that are hidden.
147+ * @param hiddenPerspectives The list of hidden perspectives
148+ */
149+ private void setVisiblePerspectives (List <String > hiddenPerspectives ) {
150+ Map <String , Boolean > visibilityMap = new HashMap <String , Boolean >();
151+ for (MPerspective perspective : perspectivesProvider .getInitialPerspectives ()) {
152+ visibilityMap .put (perspective .getElementId (), !hiddenPerspectives .contains (perspective .getElementId ()));
153+ }
154+ setVisiblePerspectivesAsync (visibilityMap );
155+ }
156+
157+ /**
158+ * Set which perspectives are visible based on a map of the perspective ID against and true if visible, false if hidden.
159+ * @param visiblePerspectiveMap A map of perspective ID vs true if visible, false if hidden.
160+ */
161+ private void setVisiblePerspectivesAsync (Map <String , Boolean > visiblePerspectiveMap ) {
162+ Display .getDefault ().asyncExec (new Runnable () {
163+ @ Override
164+ public void run () {
165+ List <MPerspective > perspectives = perspectiveStack .getChildren ();
166+ if (perspectives .size () > 0 ) {
167+ perspectives .forEach (c -> c .setVisible (true ));
168+ perspectives .forEach (c -> c .setVisible (visiblePerspectiveMap .getOrDefault (c .getElementId (), true )));
169+ if (!perspectiveStack .getSelectedElement ().isVisible ()) {
170+ // If selected perspective is no longer visible then
171+ // find first visible perspective and set it as current
172+ perspectiveStack .setSelectedElement (perspectives .stream ()
173+ .filter (c -> c .isVisible ()).findFirst ().orElse (perspectives .get (0 )));
174+ }
175+ } else {
176+ LOG .error ("No perspectives available to show." );
177+ }
178+ }
179+ });
180+ }
181+
72182 /**
73183 * Listen to perspective changes and set the new perspective as current
74184 * perspective in ResetLayoutButtonModel.
75185 *
76- * @param broker
77- * IEventBroker
78- * @param perspective
79- * The new current perspective
186+ * @param broker IEventBroker
187+ * @param perspective The new current perspective
80188 */
81- public void subscribeSelectedPerspective (IEventBroker broker , MPerspective perspective ) {
189+ private void subscribeSelectedPerspective (IEventBroker broker , MPerspective perspective ) {
82190
83191 EventHandler handler = new EventHandler () {
84192 @ Override
85193 public void handleEvent (Event event ) {
86194 MUIElement element = (MUIElement ) event .getProperty (EventTags .NEW_VALUE );
87195
88- if (!perspectiveStack .getSelectedElement ().equals (perspective )) {
196+ if (perspectiveStack .getSelectedElement () != null
197+ && !perspectiveStack .getSelectedElement ().equals (perspective )) {
89198 return ;
90199 }
91200
@@ -105,12 +214,10 @@ public void handleEvent(Event event) {
105214 * Listen to perspective content changes set the current perspective in
106215 * ResetLayoutButtonModel to changed.
107216 *
108- * @param broker
109- * IEventBroker
110- * @param perspective
111- * The new current perspective
217+ * @param broker IEventBroker
218+ * @param perspective The new current perspective
112219 */
113- public void subscribeChangedElement (IEventBroker broker , MPerspective perspective ) {
220+ private void subscribeChangedElement (IEventBroker broker , MPerspective perspective ) {
114221
115222 EventHandler handler = new EventHandler () {
116223 boolean alreadyCalled = false ;
@@ -140,4 +247,26 @@ public void handleEvent(Event event) {
140247
141248 broker .subscribe (UIEvents .UIElement .TOPIC_CONTAINERDATA , handler );
142249 }
250+
251+ /**
252+ * Enables checking the connection of perspective PV after a variable time. After that, performs
253+ * an additional connection check.
254+ * @param time Time in milliseconds of how long to wait before enabling PV connection check.
255+ */
256+ private void setupConnectionCheckTimeout (long time ) {
257+ Thread connectionTimeoutThread = new Thread () {
258+ public void run () {
259+ try {
260+ Thread .sleep (time );
261+ connectionCheck = true ;
262+ if (!perspectiveSettings .isConnected ()) {
263+ LOG .error (PERSPECTIVE_CONFIG_PV_DISCONNECTED_MSG );
264+ }
265+ } catch (InterruptedException e ) {
266+ LOG .error ("Connection timeout interrupted." );
267+ }
268+ }
269+ };
270+ connectionTimeoutThread .start ();
271+ }
143272}
0 commit comments