Skip to content

Commit

Permalink
Merge pull request #2550 from ControlSystemStudio/CSSTUDIO-1761
Browse files Browse the repository at this point in the history
CSSTUDIO-1761 (Part 1/2) Add functionality to the menu of the main window for adding a layout to the current layout.
  • Loading branch information
abrahamwolk authored Feb 27, 2023
2 parents dea5544 + a215733 commit 4b5e844
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
public class Messages
{
// Keep in alphabetical order and aligned with messages.properties
public static String AddLayout;
public static String AllFiles;
public static String AlwaysShowTabs;
public static String Applications;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,10 @@

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
Expand Down Expand Up @@ -176,6 +169,11 @@ public class PhoebusApplication extends Application {
*/
private final Menu load_layout = new Menu(Messages.LoadLayout, ImageCache.getImageView(ImageCache.class, "/icons/layouts.png"));

/**
* Menu to add a layout to the current layout
*/
private final Menu add_layout = new Menu(Messages.AddLayout, ImageCache.getImageView(ImageCache.class, "/icons/layouts.png"));

/**
* List of memento names
*
Expand Down Expand Up @@ -540,6 +538,7 @@ private MenuBar createMenu(final Stage stage) {
new SeparatorMenuItem(),
save_layout,
load_layout,
add_layout,
delete_layouts,
new SeparatorMenuItem(),
/* Full Screen placeholder */
Expand Down Expand Up @@ -592,6 +591,7 @@ void createLoadLayoutsMenu() {

final List<MenuItem> menuItemList = new ArrayList<>();
final List<MenuItem> toolbarMenuItemList = new ArrayList<>();
final List<MenuItem> addLayoutMenuItemList = new ArrayList<>();

final Map<String, File> layoutFiles = new HashMap<String, File>();

Expand Down Expand Up @@ -646,6 +646,12 @@ void createLoadLayoutsMenu() {
toolbarMenuItem.setMnemonicParsing(false);
toolbarMenuItem.setOnAction(event -> startLayoutReplacement(file));
toolbarMenuItemList.add(toolbarMenuItem);

// Create menu for adding a layout:
final MenuItem addLayoutMenuItem = new MenuItem(filename);
addLayoutMenuItem.setMnemonicParsing(false);
addLayoutMenuItem.setOnAction(event -> startAddingLayout(file));
addLayoutMenuItemList.add(addLayoutMenuItem);
}
});
}
Expand All @@ -654,6 +660,7 @@ void createLoadLayoutsMenu() {
Platform.runLater(() ->
{
load_layout.getItems().setAll(menuItemList);
add_layout.getItems().setAll(addLayoutMenuItemList);
layout_menu_button.getItems().setAll(toolbarMenuItemList);
delete_layouts.setDisable(memento_files.isEmpty());
});
Expand Down Expand Up @@ -1011,6 +1018,28 @@ private void startLayoutReplacement(final File memento_file) {
});
}

/**
* Initiate adding a layout to the current layout
*
* @param mementoFile Memento for the desired layout
*/
private void startAddingLayout(File mementoFile) {
JobManager.schedule(mementoFile.getName(), monitor ->
{
MementoTree mementoTree;
try {
mementoTree = loadMemento(mementoFile);
} catch (FileNotFoundException fileNotFoundException) {
logger.log(Level.SEVERE, "Unable to add a layout to the existing layout due to an error when opening the file '" + mementoFile.getAbsolutePath() + "'.");
return;
} catch (Exception exception) {
logger.log(Level.SEVERE, "Unable to add a layout to the existing layout due to an error when parsing the file '" + mementoFile.getAbsolutePath() + "'.");
return;
}
Platform.runLater(() -> addLayoutToCurrentLayout(mementoTree));
});
}

/**
* @param memento Memento for new layout that should replace current one
*/
Expand Down Expand Up @@ -1096,6 +1125,41 @@ private MementoTree loadMemento(final File memfile) throws Exception {
return XMLMementoTree.read(new FileInputStream(memfile));
}

/**
* Adds a layout from a MementoTree to the current layout.
*/
private void addLayoutToCurrentLayout(MementoTree mementoTree) {

List<Runnable> restoreSelectedTabFunctions = new LinkedList<>();
for (Stage stage : DockStage.getDockStages()) {
for (DockPane pane : DockStage.getDockPanes(stage)) {
DockItem tab = (DockItem) pane.getSelectionModel().getSelectedItem();
restoreSelectedTabFunctions.add(() -> tab.select());
}
}

List<Runnable> focusNewlyCreatedStageFunctions = new LinkedList<>();
for (MementoTree childMementoTree : mementoTree.getChildren()) {
Stage stage = new Stage();
DockStage.configureStage(stage);
MementoHelper.restoreStage(childMementoTree, stage);

DockStage.deferUntilAllPanesOfStageHaveScenes(stage, () ->
{
long numberOfRestoredTabsInStage = DockStage.getDockPanes(stage).stream()
.flatMap(pane -> pane.getTabs().stream())
.count();
if (numberOfRestoredTabsInStage > 0) {
focusNewlyCreatedStageFunctions.add(() -> stage.requestFocus());
} else {
stage.close();
}
restoreSelectedTabFunctions.forEach(f -> f.run());
focusNewlyCreatedStageFunctions.forEach(f -> f.run());
});
}
}

/**
* Restore stages from memento
*
Expand Down
78 changes: 49 additions & 29 deletions core/ui/src/main/java/org/phoebus/ui/docking/DockPane.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@

import java.lang.ref.WeakReference;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import org.phoebus.framework.jobs.JobManager;
import org.phoebus.ui.application.Messages;
import org.phoebus.ui.dialog.DialogHelper;
Expand Down Expand Up @@ -394,38 +394,58 @@ private StackPane findTabHeader()
return null;
}

/** Somewhat hacky:
* Need the scene of this dock pane to adjust the style sheet
private Deque<Consumer<Scene>> functionsDeferredUntilInScene = new LinkedList<>();
private boolean changeListenerAdded = false;
/** Need the scene of this dock pane to adjust the style sheet
* or to interact with the Window.
*
* We _have_ added this DockPane to a scene graph, so getScene() should
* return the scene.
* But if this dock pane is nested inside a newly created {@link SplitDock},
* it will not have a scene until it is rendered.
* So keep deferring to the next UI pulse until there is a scene.
* @param user_of_scene Something that needs to run once there is a scene
*
* If calls to deferUntilInScene() are not nested (i.e., there is no
* call of the form deferUntilInScene(f) where f() in turn contains further
* calls of the form deferUntilInScene(g) for some g), then the relative
* ordering in time of deferred function calls is preserved: if f1() is
* deferred before f2() is deferred, then f1() will be invoked before f2()
* is invoked.
*
* If, on the other hand, there *is* a call of the form deferUntilInScene(f)
* where f() in turn contains a nested call of the form deferUntilInScene(g),
* then the invocation of g() that is deferred by the call deferUntilInScene(g)
* will occur as part of the (possibly deferred) invocation of f(). I.e., it
* will *not* be deferred until after all other deferred function invocations
* have completed, but will be invoked as part of the (possibly deferred)
* invocation of f().
*
* @param function Something that needs to run once there is a scene
*/
public void deferUntilInScene(final Consumer<Scene> user_of_scene)
{
// Tried to optimize this based on
// sceneProperty().addListener(...),
// creating list of registered users_of_scene,
// invoking once the scene property changes to != null,
// then deleting the list and removing the listener,
// but that added quite some code and failed for
// strange endless-loop type reasons.
deferUntilInScene(0, user_of_scene);
}

// See deferUntilInScene, giving up after 10 attempts
private void deferUntilInScene(final int level, final Consumer<Scene> user_of_scene)
{
if (getScene() != null)
user_of_scene.accept(getScene());
else if (level < 10)
Platform.runLater(() -> deferUntilInScene(level+1, user_of_scene));
else
logger.log(Level.WARNING, this + " has no scene for deferred call to " + user_of_scene);
public void deferUntilInScene(Consumer<Scene> function) {
Scene scene = sceneProperty().get();
if (scene != null) {
function.accept(scene);
}
else {
functionsDeferredUntilInScene.addLast(function);

if (!changeListenerAdded) {
ChangeListener changeListener = new ChangeListener() {
@Override
public void changed(ObservableValue observableValue, Object oldValue, Object newValue) {
if (newValue != null) {
while(!functionsDeferredUntilInScene.isEmpty()) {
Consumer<Scene> f = functionsDeferredUntilInScene.removeFirst();
f.accept((Scene) newValue);
}
sceneProperty().removeListener(this);
}
}
};
sceneProperty().addListener(changeListener);
changeListenerAdded = true;
}
}
}

/** Hide or show tabs
Expand Down Expand Up @@ -579,7 +599,7 @@ private void handleDrop(final DragEvent event)
public SplitDock split(final boolean horizontally)
{
final SplitDock split;
if (dock_parent instanceof BorderPane)
if (dock_parent instanceof BorderPane)
{
final BorderPane parent = (BorderPane) dock_parent;
// Remove this dock pane from BorderPane
Expand Down
14 changes: 14 additions & 0 deletions core/ui/src/main/java/org/phoebus/ui/docking/DockStage.java
Original file line number Diff line number Diff line change
Expand Up @@ -417,4 +417,18 @@ else if (node instanceof SplitDock)
else
buf.append(node).append("\n");
}

private static void deferUntilAllPanesOfStageHaveScenes(Runnable runnable, List<DockPane> remainingPanes) {
// This is a helper function for implementing deferUntilAllPanesOfStageHaveScenes(Stage stage, Runnable runnable).
if (remainingPanes.size() == 0) {
runnable.run();
} else {
var pane = remainingPanes.get(0);
pane.deferUntilInScene(scene -> deferUntilAllPanesOfStageHaveScenes(runnable, remainingPanes.subList(1, remainingPanes.size())));
}
}

public static void deferUntilAllPanesOfStageHaveScenes(Stage stage, Runnable runnable) {
deferUntilAllPanesOfStageHaveScenes(runnable, getDockPanes(stage));
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
AddLayout=Add Layout
AllFiles=All Files
AlwaysShowTabs=Always Show Tabs
Applications=Applications
Expand Down

0 comments on commit 4b5e844

Please sign in to comment.