Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified app/save-and-restore/app/doc/images/filter-highlight.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/save-and-restore/app/doc/images/screenshot1.png
Comment thread
jacomago marked this conversation as resolved.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 6 additions & 4 deletions app/save-and-restore/app/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ Credentials Manager application. See also below.
Nodes and node types
--------------------

Save-and-restore data managed by the service is arranged in a tree structure and hence presented in the client UI using
a tree view UI component. In the following objects in the tree are referred to as "nodes".
Save-and-restore data managed by the service is arranged in a tree table structure and hence presented in the client UI using
a tree table view UI component. In the following objects in the tree are referred to as "nodes".

The root of the tree structure is a folder that may only contain folder nodes. Folders may contain sub-folders, configurations and composite snapshots.
The root of the tree table structure is a folder that may only contain folder nodes. Folders may contain sub-folders, configurations and composite snapshots.
The child nodes of a configuration are snapshots associated with that configuration.

Additionally a composite snapshot node may reference an arbitrary number of snapshot or composite snapshot nodes.
Expand All @@ -38,11 +38,13 @@ There are thus four node types managed in the application:
*NOTE*: If a folder or configuration node is deleted, all child nodes are unconditionally and recursively deleted. The user
is prompted to confirm delete actions as they are irreversible.

Below screen shot shows the tree structure and a configuration editor.
Below screen shot shows the tree table structure and a configuration editor.

.. image:: images/screenshot1.png
:width: 80%

The + icon to the left of the table headers launches a menu from which user can choose to hide or show columns.

Annotations on nodes
--------------------

Expand Down
Original file line number Diff line number Diff line change
@@ -1,144 +1,23 @@
/**
* Copyright (C) 2019 European Spallation Source ERIC.
* <p>
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* <p>
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* Copyright (C) 2026 European Spallation Source ERIC.
*/

package org.phoebus.applications.saveandrestore.ui;

import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;

import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.image.ImageView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication;
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.applications.saveandrestore.model.NodeType;
import org.phoebus.applications.saveandrestore.model.Tag;
import org.phoebus.ui.javafx.ImageCache;
import org.phoebus.util.time.TimestampFormats;

import java.util.ArrayList;
import java.util.List;

/**
* A cell editor managing the different type of nodes in the save-and-restore tree.
* Implements aspects like icon selection, text layout, context menu and editing.
* A cell editor managing the different type of nodes in the save-and-restore data set
* when rendered in a {@link javafx.scene.control.TreeView}.
*/
public class BrowserTreeCell extends TreeCell<Node> {

private final SaveAndRestoreController saveAndRestoreController;

private static final Border BORDER = new Border(new BorderStroke(Color.GREEN, BorderStrokeStyle.SOLID,
new CornerRadii(5.0), BorderStroke.THIN));

public BrowserTreeCell() {
this(null);
}

public BrowserTreeCell(SaveAndRestoreController saveAndRestoreController) {
this.saveAndRestoreController = saveAndRestoreController;

// This is need in order to suppress the context menu when right-clicking in a portion of the
// tree view where no tree items are rendered.
setOnMousePressed(event -> {
if (event.isSecondaryButtonDown() && event.getTarget() instanceof TreeCell) {
TreeCell<Node> treeCell = ((TreeCell<Node>) event.getTarget());
if (treeCell.getTreeItem() == null) {
setContextMenu(null);
}
}
});

setOnDragDetected(event -> {
if (saveAndRestoreController.getUserIdentity().isNull().get() || !saveAndRestoreController.selectedNodesOfSameType()) {
return;
}
final ClipboardContent content = new ClipboardContent();
Node node = getItem();
// Drag-n-drop not supported for root node
if (node != null &&
!node.getUniqueId().equals(Node.ROOT_FOLDER_UNIQUE_ID)) {
final List<Node> nodes = new ArrayList<>();

for (TreeItem<Node> sel : getTreeView().getSelectionModel().getSelectedItems()) {
nodes.add(sel.getValue());
}
content.put(SaveAndRestoreApplication.NODE_SELECTION_FORMAT, nodes);
// Only move supported!
final Dragboard db = startDragAndDrop(TransferMode.MOVE);
db.setContent(content);
}
event.consume();
});

setOnDragOver(event ->
{
final Node node = getItem();
if (node != null) {
List<Node> sourceNodes = (List<Node>) event.getDragboard().getContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT);
if (DragNDropUtil.mayDrop(event.getTransferMode(), node, sourceNodes)) {
event.acceptTransferModes(event.getTransferMode());
setBorder(BORDER);
}
}
event.consume();
});

setOnDragExited(event ->
{
setBorder(null);
event.consume();
});

setOnDragDropped(event ->
{
Node targetNode = getItem();
if (targetNode != null) {
TransferMode transferMode = event.getTransferMode();
List<Node> sourceNodes = (List<Node>) event.getDragboard().getContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT);
if (!DragNDropUtil.mayDrop(transferMode, targetNode, sourceNodes)) {
return;
}
if (DragNDropUtil.snapshotsOrCompositeSnapshotsOnly(sourceNodes) && targetNode.getNodeType().equals(NodeType.COMPOSITE_SNAPSHOT)) {
saveAndRestoreController.editCompositeSnapshot(targetNode, sourceNodes);
} else {
getTreeView().getSelectionModel().clearSelection(); // This is needed to help controller implement selection restrictions
saveAndRestoreController.moveNodes(sourceNodes, targetNode, transferMode);
}
}
event.setDropCompleted(true);
event.consume();
});

// This is to suppress expansion of the TreeItem on double-click.
addEventFilter(MouseEvent.MOUSE_PRESSED, (MouseEvent e) -> {
if (e.getClickCount() % 2 == 0 && e.getButton().equals(MouseButton.PRIMARY))
e.consume();
});
public BrowserTreeCell(){
super();
}

@Override
Expand All @@ -147,66 +26,25 @@ public void updateItem(Node node, boolean empty) {
if (empty) {
setText(null);
setGraphic(null);
setTooltip(null);
getStyleClass().remove("filter-match");
return;
}
// Use custom layout as this makes it easier to set opacity
HBox hBox = new HBox();
// saveAndRestoreController is null if configuration management is from OPI or channel table
if (saveAndRestoreController != null && !saveAndRestoreController.matchesFilter(node)) {
hBox.setOpacity(0.4);
}
StringBuilder stringBuilder = new StringBuilder();
String comment = node.getDescription();
if (comment != null && !comment.isEmpty()) {
stringBuilder.append(comment).append(System.lineSeparator());
}
if (node.getCreated() != null) { // Happens if configuration management is accessed from context menu
stringBuilder.append(TimestampFormats.SECONDS_FORMAT
.format(node.getLastModified() != null ? node.getLastModified().toInstant() : node.getCreated().toInstant())).append(" (").append(node.getUserName()).append(")");
}
// Tooltip with at least date and user id is set on all tree items
setTooltip(new Tooltip(stringBuilder.toString()));

switch (node.getNodeType()) {
case SNAPSHOT:
if (node.hasTag(Tag.GOLDEN)) {
hBox.getChildren().add(new ImageView(ImageRepository.GOLDEN_SNAPSHOT));
} else {
hBox.getChildren().add(new ImageView(ImageRepository.SNAPSHOT));
}
hBox.getChildren().add(new Label(node.getName()));
if (node.getTags() != null && !node.getTags().isEmpty()) {
ImageView tagImage = new ImageView(ImageCache.getImage(BrowserTreeCell.class, "/icons/save-and-restore/snapshot-tags.png"));
tagImage.setFitHeight(13);
tagImage.setPreserveRatio(true);
hBox.getChildren().add(tagImage);
}
setGraphic(node.hasTag(Tag.GOLDEN) ? new ImageView(ImageRepository.GOLDEN_SNAPSHOT)
: new ImageView(ImageRepository.SNAPSHOT));
break;
case COMPOSITE_SNAPSHOT:
hBox.getChildren().add(new ImageView(ImageRepository.COMPOSITE_SNAPSHOT));
hBox.getChildren().add(new Label(node.getName()));
if (node.getTags() != null && !node.getTags().isEmpty()) {
ImageView tagImage = new ImageView(ImageCache.getImage(BrowserTreeCell.class, "/icons/save-and-restore/snapshot-tags.png"));
tagImage.setFitHeight(13);
tagImage.setPreserveRatio(true);
getChildren().add(tagImage);
}
setGraphic(new ImageView(ImageRepository.COMPOSITE_SNAPSHOT));
break;
case CONFIGURATION:
hBox.getChildren().add(new ImageView(ImageRepository.CONFIGURATION));
hBox.getChildren().add(new Label(node.getName()));
setGraphic(new ImageView(ImageRepository.CONFIGURATION));
break;
case FOLDER:
hBox.getChildren().add(new ImageView(ImageRepository.FOLDER));
hBox.getChildren().add(new Label(node.getName()));
if (node.getUniqueId().equals(Node.ROOT_FOLDER_UNIQUE_ID)) {
setTooltip(new Tooltip(SaveAndRestoreService.getInstance().getServiceIdentifier()));
}
setGraphic(new ImageView(ImageRepository.FOLDER));
break;
}

setGraphic(hBox);
setText(node.getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.phoebus.applications.saveandrestore.ui;

import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.control.TreeTableCell;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.applications.saveandrestore.model.NodeType;
import org.phoebus.applications.saveandrestore.model.Tag;
import org.phoebus.ui.javafx.ImageCache;
import org.phoebus.util.time.TimestampFormats;

import java.util.Date;

public class NodeLastUpdatedTreeTableCell extends TreeTableCell<Node, Node> {

private final SaveAndRestoreController saveAndRestoreController;

public NodeLastUpdatedTreeTableCell(SaveAndRestoreController saveAndRestoreController){
this.saveAndRestoreController = saveAndRestoreController;
}

@Override
public void updateItem(Node node, boolean empty){
super.updateItem(node, empty);
if (empty) {
setText(null);
return;
}

if(node.getNodeType().equals(NodeType.FOLDER)){
setText(null);
}
else{
setText(TimestampFormats.SECONDS_FORMAT.format(node.getLastModified().toInstant()));
}
setOpacity(saveAndRestoreController.matchesFilter(node) ? 1.0 : 0.4);
}
}
Loading
Loading