forked from phoedos/pmd
Move auxclasspath in view menu, record new exceptions
This commit is contained in:
@ -138,8 +138,8 @@ public class MainDesignerController extends AbstractController {
|
||||
|
||||
openEventLogMenuItem.setOnAction(e -> eventLogController.showPopup());
|
||||
openEventLogMenuItem.textProperty().bind(
|
||||
eventLogController.numLogEntriesProperty()
|
||||
.map(i -> "Open event log (" + i + " entries)"));
|
||||
designerRoot.getLogger().numNewLogEntriesProperty().map(i -> "Exception log (" + (i > 0 ? i : "no") + " new)")
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
@ -4,30 +4,87 @@
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.model;
|
||||
|
||||
import java.util.Objects;
|
||||
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.PARSE_EXCEPTION;
|
||||
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.PARSE_OK;
|
||||
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.XPATH_EVALUATION_EXCEPTION;
|
||||
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.XPATH_OK;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.EnumSet;
|
||||
|
||||
import org.reactfx.EventSource;
|
||||
import org.reactfx.EventStream;
|
||||
import org.reactfx.EventStreams;
|
||||
import org.reactfx.collection.LiveArrayList;
|
||||
import org.reactfx.collection.LiveList;
|
||||
import org.reactfx.value.Val;
|
||||
|
||||
import net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
|
||||
|
||||
|
||||
/**
|
||||
* Logs events.
|
||||
* Logs events. Stores the whole log in case no view was open.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class EventLogger {
|
||||
|
||||
/**
|
||||
* Exceptions from XPath evaluation or parsing are never emitted
|
||||
* within less than that time interval to keep them from flooding the tableview.
|
||||
*/
|
||||
private static final Duration PARSE_EXCEPTION_DELAY = Duration.ofMillis(3000);
|
||||
private final EventSource<LogEntry> latestEvent = new EventSource<>();
|
||||
private final LiveList<LogEntry> fullLog = new LiveArrayList<>();
|
||||
|
||||
|
||||
public EventLogger() {
|
||||
|
||||
EventStream<LogEntry> onlyParseException =
|
||||
latestEvent.filter(x -> x.getCategory() == PARSE_EXCEPTION || x.getCategory() == PARSE_OK)
|
||||
.successionEnds(PARSE_EXCEPTION_DELAY)
|
||||
// don't output anything when the last state recorded was OK
|
||||
.filter(x -> x.getCategory() != PARSE_OK);
|
||||
|
||||
EventStream<LogEntry> onlyXPathException =
|
||||
latestEvent.filter(x -> x.getCategory() == XPATH_EVALUATION_EXCEPTION || x.getCategory() == XPATH_OK)
|
||||
.successionEnds(PARSE_EXCEPTION_DELAY)
|
||||
// don't output anything when the last state recorded was OK
|
||||
.filter(x -> x.getCategory() != XPATH_OK);
|
||||
|
||||
EnumSet<Category> otherExceptionSet = EnumSet.complementOf(EnumSet.of(PARSE_EXCEPTION, XPATH_EVALUATION_EXCEPTION, PARSE_OK, XPATH_OK));
|
||||
|
||||
EventStream<LogEntry> otherExceptions = latestEvent.filter(x -> otherExceptionSet.contains(x.getCategory()));
|
||||
|
||||
EventStreams.merge(onlyParseException, otherExceptions, onlyXPathException)
|
||||
.subscribe(fullLog::add);
|
||||
}
|
||||
|
||||
|
||||
/** Number of log entries that were not yet examined by the user. */
|
||||
public Val<Integer> numNewLogEntriesProperty() {
|
||||
return DesignerUtil.countNotMatching(fullLog.map(LogEntry::wasExaminedProperty));
|
||||
}
|
||||
|
||||
|
||||
/** Total number of log entries. */
|
||||
public Val<Integer> numLogEntriesProperty() {
|
||||
return fullLog.sizeProperty();
|
||||
}
|
||||
|
||||
|
||||
public void logEvent(LogEntry event) {
|
||||
latestEvent.push(event);
|
||||
if (event != null) {
|
||||
latestEvent.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream that emits an event each time an exception is logged by some
|
||||
* part of the application.
|
||||
* Returns the full log.
|
||||
*/
|
||||
public EventStream<LogEntry> getLog() {
|
||||
return latestEvent.filter(Objects::nonNull);
|
||||
public LiveList<LogEntry> getLog() {
|
||||
return fullLog;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ package net.sourceforge.pmd.util.fxdesigner.model;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.reactfx.value.Var;
|
||||
|
||||
|
||||
/**
|
||||
* Log entry of an {@link EventLogger}.
|
||||
@ -20,7 +22,7 @@ public class LogEntry {
|
||||
private final Throwable throwable;
|
||||
private final Category category;
|
||||
private final Date timestamp;
|
||||
|
||||
private Var<Boolean> wasExamined = Var.newSimpleVar(false);
|
||||
|
||||
public LogEntry(Throwable thrown, Category cat) {
|
||||
this.throwable = thrown;
|
||||
@ -29,6 +31,20 @@ public class LogEntry {
|
||||
}
|
||||
|
||||
|
||||
public boolean isWasExamined() {
|
||||
return wasExamined.getValue();
|
||||
}
|
||||
|
||||
|
||||
public void setExamined(boolean wasExamined) {
|
||||
this.wasExamined.setValue(wasExamined);
|
||||
}
|
||||
|
||||
public Var<Boolean> wasExaminedProperty() {
|
||||
return wasExamined;
|
||||
}
|
||||
|
||||
|
||||
public Throwable getThrown() {
|
||||
return throwable;
|
||||
}
|
||||
|
@ -4,24 +4,16 @@
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.popups;
|
||||
|
||||
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.PARSE_EXCEPTION;
|
||||
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.PARSE_OK;
|
||||
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.XPATH_EVALUATION_EXCEPTION;
|
||||
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.XPATH_OK;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.reactfx.EventStream;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.reactfx.EventStreams;
|
||||
import org.reactfx.value.Val;
|
||||
import org.reactfx.Subscription;
|
||||
import org.reactfx.value.Var;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
@ -34,6 +26,9 @@ import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.SoftReferenceCache;
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
@ -41,6 +36,7 @@ import javafx.scene.Scene;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableColumn.SortType;
|
||||
import javafx.scene.control.TableRow;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
@ -49,6 +45,8 @@ import javafx.stage.Stage;
|
||||
|
||||
|
||||
/**
|
||||
* A presenter over the {@link net.sourceforge.pmd.util.fxdesigner.model.EventLogger}.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.0.0
|
||||
*/
|
||||
@ -59,6 +57,7 @@ public class EventLogController extends AbstractController {
|
||||
* within less than that time interval to keep them from flooding the tableview.
|
||||
*/
|
||||
private static final Duration PARSE_EXCEPTION_DELAY = Duration.ofMillis(3000);
|
||||
private static final PseudoClass NEW_ENTRY = PseudoClass.getPseudoClass("new-entry");
|
||||
|
||||
private final DesignerRoot designerRoot;
|
||||
private final MainDesignerController mediator;
|
||||
@ -66,7 +65,7 @@ public class EventLogController extends AbstractController {
|
||||
@FXML
|
||||
private TableView<LogEntry> eventLogTableView;
|
||||
@FXML
|
||||
private TableColumn<LogEntry, Date> logDateColumn;
|
||||
private TableColumn<LogEntry, LogEntry> logDateColumn;
|
||||
@FXML
|
||||
private TableColumn<LogEntry, Category> logCategoryColumn;
|
||||
@FXML
|
||||
@ -79,7 +78,9 @@ public class EventLogController extends AbstractController {
|
||||
|
||||
private SoftReferenceCache<Stage> popupStageCache = new SoftReferenceCache<>(this::createStage);
|
||||
|
||||
private Var<Integer> numLogEntries = Var.newSimpleVar(0);
|
||||
// subscription allowing to unbind from the popup. If that's not done,
|
||||
// the popup fxml is always bound to the controller we have a memory leak
|
||||
private Subscription popupBinding = () -> {};
|
||||
|
||||
public EventLogController(DesignerRoot owner, MainDesignerController mediator) {
|
||||
this.designerRoot = owner;
|
||||
@ -87,66 +88,41 @@ public class EventLogController extends AbstractController {
|
||||
}
|
||||
|
||||
|
||||
// this is only called each time a popup is created
|
||||
@Override
|
||||
protected void beforeParentInit() {
|
||||
|
||||
popupBinding.unsubscribe();
|
||||
|
||||
logCategoryColumn.setCellValueFactory(new PropertyValueFactory<>("category"));
|
||||
logMessageColumn.setCellValueFactory(new PropertyValueFactory<>("message"));
|
||||
final DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
|
||||
logDateColumn.setCellValueFactory(entry -> new SimpleObjectProperty<>(entry.getValue().getTimestamp()));
|
||||
logDateColumn.setCellFactory(column -> new TableCell<LogEntry, Date>() {
|
||||
logDateColumn.setCellValueFactory(entry -> new SimpleObjectProperty<>(entry.getValue()));
|
||||
logDateColumn.setCellFactory(column -> new TableCell<LogEntry, LogEntry>() {
|
||||
|
||||
Subscription sub = null;
|
||||
|
||||
|
||||
@Override
|
||||
protected void updateItem(Date item, boolean empty) {
|
||||
protected void updateItem(LogEntry item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (sub != null) {
|
||||
sub.unsubscribe();
|
||||
}
|
||||
if (item == null || empty) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
} else {
|
||||
setText(dateFormat.format(item));
|
||||
setText(dateFormat.format(item.getTimestamp()));
|
||||
sub = item.wasExaminedProperty()
|
||||
.map(wasExamined -> wasExamined ? null : new FontIcon("fas-exclamation-circle"))
|
||||
.values()
|
||||
.subscribe(graphicProperty()::setValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EventStream<LogEntry> onlyParseException = designerRoot.getLogger().getLog()
|
||||
.filter(x -> x.getCategory() == PARSE_EXCEPTION || x.getCategory() == PARSE_OK)
|
||||
.successionEnds(PARSE_EXCEPTION_DELAY)
|
||||
// don't output anything when the last state recorded was OK
|
||||
.filter(x -> x.getCategory() != PARSE_OK);
|
||||
|
||||
EventStream<LogEntry> onlyXPathException = designerRoot.getLogger().getLog()
|
||||
.filter(x -> x.getCategory() == XPATH_EVALUATION_EXCEPTION || x.getCategory() == XPATH_OK)
|
||||
.successionEnds(PARSE_EXCEPTION_DELAY)
|
||||
// don't output anything when the last state recorded was OK
|
||||
.filter(x -> x.getCategory() != XPATH_OK);
|
||||
|
||||
EnumSet<Category> otherExceptionSet = EnumSet.complementOf(EnumSet.of(PARSE_EXCEPTION, XPATH_EVALUATION_EXCEPTION, PARSE_OK, XPATH_OK));
|
||||
|
||||
EventStream<LogEntry> otherExceptions = designerRoot.getLogger().getLog()
|
||||
.filter(x -> otherExceptionSet.contains(x.getCategory()));
|
||||
|
||||
EventStreams.merge(onlyParseException, otherExceptions, onlyXPathException)
|
||||
.hook(e -> numLogEntries.setValue(numLogEntries.getValue() + 1))
|
||||
.subscribe(t -> eventLogTableView.getItems().add(t));
|
||||
|
||||
eventLogTableView.getSelectionModel()
|
||||
.selectedItemProperty()
|
||||
.addListener((obs, oldVal, newVal) -> onExceptionSelectionChanges(oldVal, newVal));
|
||||
|
||||
EventStreams.combine(EventStreams.changesOf(eventLogTableView.focusedProperty()),
|
||||
EventStreams.changesOf(selectedErrorNodes));
|
||||
|
||||
EventStreams.valuesOf(eventLogTableView.focusedProperty())
|
||||
.successionEnds(Duration.ofMillis(100))
|
||||
.subscribe(b -> {
|
||||
if (b) {
|
||||
mediator.handleSelectedNodeInError(selectedErrorNodes.getValue());
|
||||
} else {
|
||||
mediator.resetSelectedErrorNodes();
|
||||
}
|
||||
});
|
||||
|
||||
selectedErrorNodes.values().subscribe(mediator::handleSelectedNodeInError);
|
||||
|
||||
eventLogTableView.resizeColumn(logMessageColumn, -1);
|
||||
|
||||
logMessageColumn.prefWidthProperty()
|
||||
@ -156,6 +132,55 @@ public class EventLogController extends AbstractController {
|
||||
.subtract(2)); // makes it work
|
||||
logDateColumn.setSortType(SortType.DESCENDING);
|
||||
|
||||
eventLogTableView.setRowFactory(tv -> {
|
||||
TableRow<LogEntry> row = new TableRow<>();
|
||||
ChangeListener<Boolean> examinedListener = (obs, oldVal, newVal) -> row.pseudoClassStateChanged(NEW_ENTRY, !newVal);
|
||||
row.itemProperty().addListener((obs, previousEntry, currentEntry) -> {
|
||||
if (previousEntry != null) {
|
||||
previousEntry.wasExaminedProperty().removeListener(examinedListener);
|
||||
}
|
||||
if (currentEntry != null) {
|
||||
currentEntry.wasExaminedProperty().addListener(examinedListener);
|
||||
row.pseudoClassStateChanged(NEW_ENTRY, !currentEntry.isWasExamined());
|
||||
} else {
|
||||
row.pseudoClassStateChanged(NEW_ENTRY, false);
|
||||
}
|
||||
});
|
||||
return row;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
private Subscription bindPopupToController() {
|
||||
Subscription binding =
|
||||
EventStreams.valuesOf(eventLogTableView.getSelectionModel().selectedItemProperty())
|
||||
.distinct()
|
||||
.subscribe(this::onExceptionSelectionChanges);
|
||||
|
||||
binding = binding.and(
|
||||
EventStreams.valuesOf(eventLogTableView.focusedProperty())
|
||||
.successionEnds(Duration.ofMillis(100))
|
||||
.subscribe(b -> {
|
||||
if (b) {
|
||||
mediator.handleSelectedNodeInError(selectedErrorNodes.getValue());
|
||||
} else {
|
||||
mediator.resetSelectedErrorNodes();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
binding = binding.and(
|
||||
selectedErrorNodes.values().subscribe(mediator::handleSelectedNodeInError)
|
||||
);
|
||||
|
||||
eventLogTableView.itemsProperty().setValue(designerRoot.getLogger().getLog());
|
||||
|
||||
binding = binding.and(
|
||||
() -> eventLogTableView.itemsProperty().setValue(FXCollections.emptyObservableList())
|
||||
);
|
||||
|
||||
return binding;
|
||||
}
|
||||
|
||||
|
||||
@ -164,6 +189,9 @@ public class EventLogController extends AbstractController {
|
||||
selectedErrorNodes.setValue(Collections.emptyList());
|
||||
return;
|
||||
}
|
||||
|
||||
entry.setExamined(true);
|
||||
|
||||
switch (entry.getCategory()) {
|
||||
case OTHER:
|
||||
break;
|
||||
@ -182,25 +210,22 @@ public class EventLogController extends AbstractController {
|
||||
|
||||
public void showPopup() {
|
||||
popupStageCache.getValue().show();
|
||||
popupBinding = bindPopupToController();
|
||||
}
|
||||
|
||||
|
||||
public void hidePopup() {
|
||||
popupStageCache.getValue().hide();
|
||||
}
|
||||
|
||||
|
||||
private void onExceptionSelectionChanges(LogEntry oldVal, LogEntry newVal) {
|
||||
logDetailsTextArea.setText(newVal == null ? "" : newVal.getStackTrace());
|
||||
|
||||
if (!Objects.equals(newVal, oldVal)) {
|
||||
handleSelectedEntry(newVal);
|
||||
if (popupStageCache.hasValue()) {
|
||||
popupStageCache.getValue().hide();
|
||||
popupBinding.unsubscribe();
|
||||
popupBinding = () -> {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Val<Integer> numLogEntriesProperty() {
|
||||
return numLogEntries;
|
||||
private void onExceptionSelectionChanges(LogEntry newVal) {
|
||||
logDetailsTextArea.setText(newVal == null ? "" : newVal.getStackTrace());
|
||||
handleSelectedEntry(newVal);
|
||||
}
|
||||
|
||||
|
||||
|
@ -15,14 +15,21 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.reactfx.EventStream;
|
||||
import org.reactfx.EventStreams;
|
||||
import org.reactfx.collection.LiveList;
|
||||
import org.reactfx.value.Val;
|
||||
import org.reactfx.value.Var;
|
||||
|
||||
import net.sourceforge.pmd.lang.Language;
|
||||
@ -34,6 +41,7 @@ import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
@ -263,4 +271,41 @@ public final class DesignerUtil {
|
||||
public static Var<Boolean> booleanVar(BooleanProperty p) {
|
||||
return Var.mapBidirectional(p, Boolean::booleanValue, Function.identity());
|
||||
}
|
||||
|
||||
|
||||
// Creating a real function Val<LiveList<T>> => LiveList<T> or LiveList<Val<T>> => LiveList<T> would
|
||||
// allow implementing LiveList.flatMap, which is a long-standing feature request in ReactFX
|
||||
// These utilities are very inefficient, but sufficient for our use case...
|
||||
public static <T> Val<LiveList<T>> flatMapChanges(ObservableList<? extends ObservableValue<T>> listOfObservables) {
|
||||
|
||||
// every time an element changes an invalidation stream
|
||||
EventStream<?> invalidations =
|
||||
LiveList.map(listOfObservables, EventStreams::valuesOf)
|
||||
.reduce(EventStreams::merge)
|
||||
.values()
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(Function.identity());
|
||||
|
||||
return Val.create(() -> LiveList.map(listOfObservables, ObservableValue::getValue), invalidations);
|
||||
}
|
||||
|
||||
|
||||
public static <T, U> Val<U> reduceWElts(ObservableList<? extends ObservableValue<T>> list, U zero, BiFunction<U, T, U> mapper) {
|
||||
return flatMapChanges(list).map(l -> l.stream().reduce(zero, mapper, (u, v) -> v));
|
||||
}
|
||||
|
||||
|
||||
public static <T> Val<Integer> countMatching(ObservableList<? extends ObservableValue<T>> list, Predicate<? super T> predicate) {
|
||||
return reduceWElts(list, 0, (cur, t) -> predicate.test(t) ? cur + 1 : cur);
|
||||
}
|
||||
|
||||
|
||||
public static Val<Integer> countMatching(ObservableList<? extends ObservableValue<Boolean>> list) {
|
||||
return countMatching(list, b -> b);
|
||||
}
|
||||
|
||||
|
||||
public static Val<Integer> countNotMatching(ObservableList<? extends ObservableValue<Boolean>> list) {
|
||||
return countMatching(list, b -> !b);
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,10 @@ public class SoftReferenceCache<T> {
|
||||
}
|
||||
|
||||
|
||||
public boolean hasValue() {
|
||||
return myRef != null && myRef.get() != null;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
if (myRef == null || myRef.get() == null) {
|
||||
T val = mySupplier.get();
|
||||
|
@ -19,6 +19,7 @@
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
|
||||
<?import org.kordamp.ikonli.javafx.FontIcon?>
|
||||
<AnchorPane prefHeight="750.0" prefWidth="1200.0" stylesheets="@../css/designer.css" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="net.sourceforge.pmd.util.fxdesigner.MainDesignerController">
|
||||
<children>
|
||||
<BorderPane prefHeight="600.0" prefWidth="900.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
@ -27,7 +28,7 @@
|
||||
<children>
|
||||
<MenuBar layoutX="-11.0" maxHeight="20.0" prefHeight="20.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="-11.0" AnchorPane.rightAnchor="11.0" AnchorPane.topAnchor="0.0">
|
||||
<menus>
|
||||
<Menu fx:id="fileMenu" mnemonicParsing="false" text="File">
|
||||
<Menu fx:id="fileMenu" text="File">
|
||||
<items>
|
||||
<MenuItem fx:id="openFileMenuItem" mnemonicParsing="false" text="Open..." />
|
||||
<Menu fx:id="openRecentMenu" mnemonicParsing="false" text="Open recent" />
|
||||
@ -35,26 +36,30 @@
|
||||
</Menu>
|
||||
|
||||
|
||||
<Menu mnemonicParsing="false" text="View">
|
||||
<Menu text="View">
|
||||
<items>
|
||||
<MenuItem fx:id="openEventLogMenuItem" mnemonicParsing="false"
|
||||
text="Open event log...">
|
||||
<MenuItem fx:id="openEventLogMenuItem" mnemonicParsing="false">
|
||||
<graphic>
|
||||
<!-- Font awesome 5.5.0 -->
|
||||
<!--<FontIcon iconLiteral="fas-bolt" />-->
|
||||
</graphic>
|
||||
</MenuItem>
|
||||
<MenuItem fx:id="setupAuxclasspathMenuItem"
|
||||
mnemonicParsing="false"
|
||||
text="Analysis classpath">
|
||||
<graphic>
|
||||
<!-- TODO Font awesome 5.0.1 (apparently not supported) -->
|
||||
<!--<FontIcon iconLiteral="fab-java"/>-->
|
||||
</graphic>
|
||||
</MenuItem>
|
||||
</items>
|
||||
</Menu>
|
||||
|
||||
<Menu mnemonicParsing="false" text="Configuration">
|
||||
<items>
|
||||
<MenuItem fx:id="setupAuxclasspathMenuItem" mnemonicParsing="false" text="Set up the auxclasspath...">
|
||||
</MenuItem>
|
||||
</items>
|
||||
</Menu>
|
||||
|
||||
<Menu mnemonicParsing="false" text="About">
|
||||
<Menu text="About">
|
||||
<items>
|
||||
<!-- TODO add link to doc pages -->
|
||||
<!--<MenuItem mnemonicParsing="false" text="About" />-->
|
||||
<MenuItem fx:id="licenseMenuItem" mnemonicParsing="false" text="License..." />
|
||||
<MenuItem fx:id="licenseMenuItem" mnemonicParsing="false" text="License" />
|
||||
</items>
|
||||
</Menu>
|
||||
|
||||
|
@ -61,6 +61,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.table-row-cell:new-entry {
|
||||
-fx-background-color: lightyellow;
|
||||
}
|
||||
|
||||
.show-hide-columns-button {
|
||||
-fx-background-color: @darker-accent;
|
||||
-fx-border-color: @darker-accent-border;
|
||||
|
Reference in New Issue
Block a user