Skip to content

Commit

Permalink
fix: xdg-open is now used to reveal a file Linux file system (gluonhq…
Browse files Browse the repository at this point in the history
…#674) (gluonhq#711)

Instead of Nautilus, xdg-utils xdg-open command is now used for opening directories in Linux. Also the related error dialogs have been reworked to have a unified look and feel and one can look into the details of the related command line call in case of error. Furthermore, the exit code if the command line call is evaluated. Added proper error dialog for CSS Panel Controller in case revealing a CSS fails.
  • Loading branch information
Oliver-Loeffler authored Oct 1, 2024
1 parent d426938 commit f0c5860
Show file tree
Hide file tree
Showing 20 changed files with 454 additions and 99 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ See the [documentation](http://docs.gluonhq.com/scenebuilder/) about the new fea

For community support, go to [StackOverflow](https://stackoverflow.com/questions/tagged/scenebuilder).

### Requirements on Linux ###

On Linux systems, Scene Builder uses the `xdg-open` command to reveal files in the system's default file system browser or to open URLs in the default web browser. Most modern Linux Desktop Environments already provide the [xdg-utils](https://freedesktop.org/wiki/Software/xdg-utils/) package. If it is missing, it can be installed using the respective Linux package management tool such as `yum`, `apt-get`, `dnf` or `pacman`. The `xdg-utils` package is usually available on KDE based systems, some Arch based systems may require manual installations.

## Issues and Contributions ##

Issues can be reported to the [Issue tracker](https://github.com/gluonhq/scenebuilder/issues/)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import com.oracle.javafx.scenebuilder.kit.editor.EditorController.ControlAction;
import com.oracle.javafx.scenebuilder.kit.editor.EditorController.EditAction;
import com.oracle.javafx.scenebuilder.kit.editor.EditorPlatform;
import com.oracle.javafx.scenebuilder.kit.editor.FileBrowserRevealException;
import com.oracle.javafx.scenebuilder.kit.editor.job.Job;
import com.oracle.javafx.scenebuilder.kit.editor.panel.content.ContentPanelController;
import com.oracle.javafx.scenebuilder.kit.editor.panel.css.CssPanelController;
Expand Down Expand Up @@ -94,6 +95,8 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;

import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
Expand Down Expand Up @@ -184,6 +187,8 @@ public enum ActionStatus {
DONE
}

private static final Logger LOGGER = Logger.getLogger(DocumentWindowController.class.getName());

private final EditorController editorController = new EditorController();
private final MenuBarController menuBarController = new MenuBarController(this);
private final ContentPanelController contentPanelController = new ContentPanelController(editorController);
Expand Down Expand Up @@ -1467,14 +1472,13 @@ void onLibraryImportSelection(ActionEvent event) {
@FXML
void onLibraryRevealCustomFolder(ActionEvent event) {
String userLibraryPath = ((UserLibrary) getEditorController().getLibrary()).getPath();

// ensure that there is no mixup of forward and backward slashes.
File libraryPath = Paths.get(userLibraryPath).normalize().toFile();
try {
EditorPlatform.revealInFileBrowser(new File(userLibraryPath));
} catch(IOException x) {
final ErrorDialog errorDialog = new ErrorDialog(null);
errorDialog.setMessage(I18N.getString("alert.reveal.failure.message", getStage().getTitle()));
errorDialog.setDetails(I18N.getString("alert.reveal.failure.details"));
errorDialog.setDebugInfoWithThrowable(x);
errorDialog.showAndWait();
EditorPlatform.revealInFileBrowser(libraryPath);
} catch (Exception revealError) {
handleRevealFolderException(revealError, String.valueOf(libraryPath));
}
}

Expand Down Expand Up @@ -2185,26 +2189,33 @@ ActionStatus performCloseAction() {
return closeConfirmed ? ActionStatus.DONE : ActionStatus.CANCELLED;
}


private void performRevealAction() {
assert editorController.getFxomDocument() != null;
assert editorController.getFxomDocument().getLocation() != null;

final URL location = editorController.getFxomDocument().getLocation();


File fxmlFile = null;
try {
/*
* Using Path.normalize().toAbsolutePath() ensures that forward and backward slashes are not mixed
* and the path matches the platform requirements. It also ensures, that the file:/ prefix is
* removed from paths and users can directly use the path in their attempt to investigate the error.
*/
fxmlFile = Path.of(location.toURI()).normalize().toAbsolutePath().toFile();
} catch (URISyntaxException e) {
handleRevealResourceException(e, String.valueOf(location));
}

try {
final File fxmlFile = new File(location.toURI());
EditorPlatform.revealInFileBrowser(fxmlFile);
} catch(IOException | URISyntaxException x) {
final ErrorDialog errorDialog = new ErrorDialog(null);
errorDialog.setMessage(I18N.getString("alert.reveal.failure.message", getStage().getTitle()));
errorDialog.setDetails(I18N.getString("alert.reveal.failure.details"));
errorDialog.setDebugInfoWithThrowable(x);
errorDialog.showAndWait();
} catch (FileBrowserRevealException re) {
handleRevealFileException(re, String.valueOf(fxmlFile));
} catch (IOException x) {
handleRevealResourceException(x, String.valueOf(fxmlFile));
}
}


private void updateLoadFileTime() {

final URL fxmlURL = editorController.getFxmlLocation();
Expand Down Expand Up @@ -2275,17 +2286,64 @@ private void performHelp() {

private void openURL(String url) {
try {
LOGGER.log(Level.FINE, "Attempting to open URL: {0}", url);
EditorPlatform.open(url);
} catch (IOException ioe) {
handleErrorWhenOpeningURL(ioe, url);
LOGGER.log(Level.WARNING, "Error during attempt to open URL!", ioe);
handleRevealResourceException(ioe, url);
}
}

private void handleErrorWhenOpeningURL(IOException ioe, String url) {
final ErrorDialog errorDialog = new ErrorDialog(null);
errorDialog.setMessage(I18N.getString("alert.help.failure.message", url));
errorDialog.setDetails(I18N.getString("alert.messagebox.failure.details"));
errorDialog.setDebugInfoWithThrowable(ioe);
/**
* When an FXML file is to be revealed and an error occurs, the file and the error are displayed in this dialog.
* The error type and its details are displayed within the details window. The dialog itself only
* notifies that there was an error.
*
* @param fileRevealException {@link FileBrowserRevealException}
* @param locationToBeRevealed {@link String}
*/
private void handleRevealFileException(FileBrowserRevealException fileRevealException, String locationToBeRevealed) {
final ErrorDialog errorDialog = new ErrorDialog(this.getStage());
errorDialog.setTitle(I18N.getString("alert.error.file.reveal.title"));
errorDialog.setMessage(I18N.getString("alert.error.file.reveal.message"));
errorDialog.setDetails(I18N.getString("alert.error.file.reveal.details", locationToBeRevealed));
errorDialog.setDetailsTitle(I18N.getString("alert.error.file.reveal.details.title"));
errorDialog.setDebugInfoWithThrowable(fileRevealException);
errorDialog.showAndWait();
}

/**
*
* Resources may be related to files included with Scene Builder or with arbitrary files (e.g. CSS sheets)
* co-located to a given FXML file. In case of an error opening a resource, a custom dialog is shown.
*
* @param revealException {@link Exception}
* @param locationToBeRevealed {@link String}
*/
private void handleRevealResourceException(Exception revealException, String locationToBeRevealed) {
final ErrorDialog errorDialog = new ErrorDialog(this.getStage());
errorDialog.setTitle(I18N.getString("alert.error.resource.reveal.title"));
errorDialog.setMessage(I18N.getString("alert.error.resource.reveal.message"));
errorDialog.setDetails(I18N.getString("alert.error.resource.reveal.details", locationToBeRevealed));
errorDialog.setDetailsTitle(I18N.getString("alert.error.resource.reveal.details.title"));
errorDialog.setDebugInfoWithThrowable(revealException);
errorDialog.showAndWait();
}

/**
*
* In some cases the resource to be revealed or opened is a directory. Hence the UI dialog should reflect this.
*
* @param revealException {@link Exception}
* @param directoryToBeRevealed {@link String}
*/
private void handleRevealFolderException(Exception revealException, String directoryToBeRevealed) {
final ErrorDialog errorDialog = new ErrorDialog(this.getStage());
errorDialog.setTitle(I18N.getString("alert.error.directory.reveal.title"));
errorDialog.setMessage(I18N.getString("alert.error.directory.reveal.message"));
errorDialog.setDetails(I18N.getString("alert.error.directory.reveal.details", directoryToBeRevealed));
errorDialog.setDetailsTitle(I18N.getString("alert.error.directory.reveal.details.title"));
errorDialog.setDebugInfoWithThrowable(revealException);
errorDialog.showAndWait();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.oracle.javafx.scenebuilder.app.i18n.I18N;
import com.oracle.javafx.scenebuilder.kit.editor.EditorController;
import com.oracle.javafx.scenebuilder.kit.editor.EditorPlatform;
import com.oracle.javafx.scenebuilder.kit.editor.FileBrowserRevealException;
import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.ErrorDialog;
import javafx.stage.FileChooser;

Expand Down Expand Up @@ -97,10 +98,11 @@ public void performRevealResource() {
try {
EditorPlatform.revealInFileBrowser(resourceFile);
} catch (IOException ioe) {
final ErrorDialog errorDialog = new ErrorDialog(null);
errorDialog.setTitle(I18N.getString("error.file.reveal.title"));
errorDialog.setMessage(I18N.getString("error.file.reveal.message"));
errorDialog.setDetails(I18N.getString("error.filesystem.details"));
final ErrorDialog errorDialog = new ErrorDialog(documentWindowController.getStage());
errorDialog.setTitle(I18N.getString("alert.error.file.reveal.title"));
errorDialog.setMessage(I18N.getString("alert.error.file.reveal.message"));
errorDialog.setDetails(I18N.getString("alert.error.file.reveal.details", resourceFile));
errorDialog.setDetailsTitle(I18N.getString("alert.error.file.reveal.details.title"));
errorDialog.setDebugInfoWithThrowable(ioe);
errorDialog.showAndWait();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/*
* Copyright (c) 2024, Gluon and/or its affiliates.
* Copyright (c) 2012, 2014, Oracle and/or its affiliates.
* All rights reserved. Use is subject to license terms.
*
Expand Down Expand Up @@ -102,10 +103,11 @@ public void performOpenSceneStyleSheet(File toOpen) {
try {
EditorPlatform.open(toOpen.getPath());
} catch (IOException ioe) {
final ErrorDialog errorDialog = new ErrorDialog(null);
final ErrorDialog errorDialog = new ErrorDialog(documentWindowController.getStage());
errorDialog.setTitle(I18N.getString("error.file.open.title"));
errorDialog.setMessage(I18N.getString("error.file.open.message"));
errorDialog.setMessage(I18N.getString("error.file.open.message", toOpen));
errorDialog.setDetails(I18N.getString("error.filesystem.details"));
errorDialog.setDetailsTitle(I18N.getString("error.file.open.title"));
errorDialog.setDebugInfoWithThrowable(ioe);
errorDialog.showAndWait();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,8 +417,6 @@ alert.messagebox.failure.message = Could not process open request
alert.messagebox.failure.details = An unexpected failure happened during open operation. Please report this failure to support.
alert.revert.question.message = Do you want to revert to the last saved version of ''{0}'' ?
alert.revert.question.details = Your current changes will be lost.
alert.reveal.failure.message = Could not reveal ''{0}''
alert.reveal.failure.details = Reveal operation has failed. Check logs of the operating system.
alert.copy.failure.message = Could not copy ''{0}''
alert.help.failure.message = Could not access to ''{0}''
alert.delete.fxid1of1.message = This component has an fx:id. Do you really want to delete it ?
Expand Down Expand Up @@ -470,11 +468,29 @@ file.filter.label.image = Image Document
file.filter.label.media = Media Document
file.filter.label.video = Video Document
error.file.open.title = File opening failed
error.file.open.details.title = File opening failed - Details
# {0} is a file path
error.file.open.message = File {0} cannot be opened
error.filesystem.details = Please check your file system.
error.file.reveal.title = File locating failed
error.file.reveal.message = File {0} cannot be found
error.filesystem.details = File: {0}

# File Reveal Error Dialog
alert.error.file.reveal.title = Failed to reveal file ...
alert.error.file.reveal.message = Cannot reveal file in file system viewer due to an error.
alert.error.file.reveal.details = ''{0}''
alert.error.file.reveal.details.title = File Reveal Error Details

# Directory Reveal Error Dialog
alert.error.directory.reveal.title = Failed to reveal folder ...
alert.error.directory.reveal.message = Cannot reveal folder in file system viewer due to an error.
alert.error.directory.reveal.details = ''{0}''
alert.error.directory.reveal.details.title = Folder Reveal Error Details

# Resource Reveal Error Dialog
alert.error.resource.reveal.title = Failed to reveal resource ...
alert.error.resource.reveal.message = Cannot reveal resource in file system viewer due to an error.
alert.error.resource.reveal.details = ''{0}''
alert.error.resource.reveal.details.title = Resource Reveal Error Details

#
log.start = JavaFX Scene Builder started
log.stop = JavaFX Scene Builder stopped
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,6 @@ alert.messagebox.failure.message = 開く操作を処理できませんでした
alert.messagebox.failure.details = 開く操作中に予期しない問題が起きました。この問題をサポートに報告してください。
alert.revert.question.message = 最後に保存した''{0}''に戻しますか。
alert.revert.question.details = 現在の変更は失われます。
alert.reveal.failure.message = ''{0}''を表示できませんでした
alert.reveal.failure.details = 元に戻すのに失敗しました。オペレーティング・システムのログを確認してください。
alert.copy.failure.message = コピー
alert.help.failure.message = ''{0}''にアクセスできませんでした
alert.delete.fxid1of1.message = このコンポーネントにはfx:idがあります。このコンポーネントを削除しますか。
Expand Down Expand Up @@ -457,7 +455,7 @@ file.filter.label.fxml = FXMLドキュメント
file.filter.label.image = イメージ・ドキュメント
file.filter.label.media = メディア・ドキュメント
file.filter.label.video = ビデオ・ドキュメント
error.file.open.title = ファイルを開くのに失敗しました

# {0} is a file path
error.file.open.message = ファイル{0}を開けません
error.filesystem.details = ファイル・システムを確認してください。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,6 @@ alert.messagebox.failure.message = 无法处理打开的请求
alert.messagebox.failure.details = 打开操作期间发生意外故障。请向支持部门报告此故障。
alert.revert.question.message = 是否要恢复到上次保存的 ''{0}'' 版本?
alert.revert.question.details = 您当前的更改将丢失。
alert.reveal.failure.message = 无法显示 ''{0}''
alert.reveal.failure.details = 显示操作失败。检查操作系统的日志。
alert.copy.failure.message = 无法复制 ''{0}''
alert.help.failure.message = 无法访问 ''{0}''
alert.delete.fxid1of1.message = 此组件具有 fx:id,你真的要删除它吗?
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2024, Gluon and/or its affiliates.
* All rights reserved. Use is subject to license terms.
*
* This file is available and licensed under the following license:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the distribution.
* - Neither the name of Oracle Corporation and Gluon nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.oracle.javafx.scenebuilder.kit.editor;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
* Cmd allows the execution of a given command line in a defined working directory. The execution is
* aborted after the timeout.
*/
final class Cmd {

/**
* Executes a given command line using the working directory and timeout.
*
* @param cmd The command line to be executed defined as a {@link List} of {@link String}
* @param wDir The working directory, where the process shall be executed within.
* @param timeoutSec Duration in in seconds after which the execution should be stopped.
* @return exit code of the command line as an Integer
*
* @throws IOException - if the command was not found or the program execution exceeds the given timeout duration.
* @throws InterruptedException - if the current thread is interrupted while waiting
*/
public final Integer exec(List<String> cmd, File wDir, long timeoutSec) throws IOException,
InterruptedException {
ProcessBuilder builder = new ProcessBuilder(cmd);
builder = builder.directory(wDir);
Process proc = builder.start();
boolean completed = proc.waitFor(timeoutSec, TimeUnit.SECONDS);
if (completed) {
return proc.exitValue();
}
throw new IOException("Process timed out after %s seconds!".formatted(timeoutSec));
}

}
Loading

0 comments on commit f0c5860

Please sign in to comment.