/** * erlyberly, erlang trace debugger * Copyright (C) 2016 Andy Till * * 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 3 of the License, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package erlyberly; import com.ericsson.otp.erlang.OtpErlangList; import com.ericsson.otp.erlang.OtpErlangObject; import floatyfield.FloatyFieldView; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.WeakChangeListener; import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import javafx.css.PseudoClass; import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.control.Control; import javafx.scene.control.Label; import javafx.scene.control.SelectionMode; import javafx.scene.control.SplitPane; import javafx.scene.control.TableColumn; import javafx.scene.control.TableRow; import javafx.scene.control.TableView; import javafx.scene.control.TitledPane; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; public class DbgTraceView extends VBox { private final DbgController dbgController; private final SortedList<TraceLog> sortedTtraces; private final FilteredList<TraceLog> filteredTraces; private final TableView<TraceLog> tracesBox; public DbgTraceView(DbgController aDbgController) { dbgController = aDbgController; sortedTtraces = new SortedList<TraceLog>(dbgController.getTraceLogs()); filteredTraces = new FilteredList<TraceLog>(sortedTtraces); setSpacing(5d); setStyle("-fx-background-insets: 5;"); setMaxHeight(Double.MAX_VALUE); tracesBox = new TableView<TraceLog>(); tracesBox.getStyleClass().add("trace-log-table"); tracesBox.setOnMouseClicked(this::onTraceClicked); tracesBox.setMaxHeight(Double.MAX_VALUE); VBox.setVgrow(tracesBox, Priority.ALWAYS); putTableColumns(); // #47 double wrapping the filtered list in another sorted list, otherwise // the table cannot be sorted on columns. Binding the items will throw exceptions // when sorting on columns. // see http://code.makery.ch/blog/javafx-8-tableview-sorting-filtering/ SortedList<TraceLog> sortedData = new SortedList<>(filteredTraces); sortedData.comparatorProperty().bind(tracesBox.comparatorProperty()); tracesBox.setItems(sortedData); putTraceContextMenu(); Parent p = traceLogFloatySearchControl(); getChildren().addAll(p, tracesBox); } @SuppressWarnings({ "unchecked", "rawtypes" }) private void putTableColumns() { TableColumn<TraceLog,Long> seqColumn; seqColumn = new TableColumn<TraceLog,Long>("Seq."); seqColumn.setCellValueFactory(new PropertyValueFactory("instanceNum")); configureColumnWidth("seqColumnWidth", seqColumn); TableColumn<TraceLog,String> pidColumn; pidColumn = new TableColumn<TraceLog,String>("Pid"); pidColumn.setCellValueFactory(new PropertyValueFactory("pid")); configureColumnWidth("pidColumnWidth", pidColumn); TableColumn<TraceLog,String> regNameColumn; regNameColumn = new TableColumn<TraceLog,String>("Reg. Name"); regNameColumn.setCellValueFactory(new PropertyValueFactory("regName")); configureColumnWidth("regNameColumnWidth", regNameColumn); TableColumn<TraceLog,String> durationNameColumn; durationNameColumn = new TableColumn<TraceLog,String>("Duration (microseconds)"); durationNameColumn.setCellValueFactory(new PropertyValueFactory("duration")); configureColumnWidth("durationNameColumnWidth", durationNameColumn); TableColumn<TraceLog,String> functionNameColumn; functionNameColumn = new TableColumn<TraceLog,String>("Function"); functionNameColumn.setCellValueFactory(new PropertyValueFactory("function")); configureColumnWidth("functionNameColumnWidth", functionNameColumn); TableColumn<TraceLog,String> argsColumn; argsColumn = new TableColumn<TraceLog,String>("Args"); argsColumn.setCellValueFactory(new PropertyValueFactory("args")); configureColumnWidth("argsColumnWidth", argsColumn); TableColumn<TraceLog,String> resultColumn; resultColumn = new TableColumn<TraceLog,String>("Result"); resultColumn.setCellValueFactory(new PropertyValueFactory("result")); configureColumnWidth("resultColumnWidth", resultColumn); tracesBox.getColumns().setAll( seqColumn, pidColumn, regNameColumn, durationNameColumn, functionNameColumn, argsColumn, resultColumn ); // based on http://stackoverflow.com/questions/27015961/tableview-row-style PseudoClass exceptionClass = PseudoClass.getPseudoClass("exception"); PseudoClass notCompletedClass = PseudoClass.getPseudoClass("not-completed"); PseudoClass breakerRowClass = PseudoClass.getPseudoClass("breaker-row"); tracesBox.setRowFactory(tv -> { TableRow<TraceLog> row = new TableRow<>(); ChangeListener<Boolean> completeListener = (obs, oldComplete, newComplete) -> { row.pseudoClassStateChanged(exceptionClass, row.getItem().isExceptionThrower()); row.pseudoClassStateChanged(notCompletedClass, !row.getItem().isComplete()); }; row.itemProperty().addListener((obs, oldTl, tl) -> { if (oldTl != null) { oldTl.isCompleteProperty().removeListener(completeListener); } if (tl != null) { row.pseudoClassStateChanged(notCompletedClass, !row.getItem().isComplete()); if("breaker-row".equals(tl.getCssClass())) { row.pseudoClassStateChanged(breakerRowClass, true); row.pseudoClassStateChanged(exceptionClass, false); row.pseudoClassStateChanged(notCompletedClass, false); } else { tl.isCompleteProperty().addListener(completeListener); row.pseudoClassStateChanged(breakerRowClass, false); row.pseudoClassStateChanged(exceptionClass, tl.isExceptionThrower()); } } else { row.pseudoClassStateChanged(exceptionClass, false); row.pseudoClassStateChanged(notCompletedClass, false); row.pseudoClassStateChanged(breakerRowClass, false); } }); return row ; }); } private void configureColumnWidth(String widthProperty, TableColumn<TraceLog, ?> functionNameColumn) { functionNameColumn.setPrefWidth(PrefBind.getOrDefaultDouble(widthProperty, functionNameColumn.getPrefWidth())); functionNameColumn.widthProperty().addListener((o, ov, nv) -> { PrefBind.set(widthProperty, nv); }); } private void putTraceContextMenu() { TraceContextMenu traceContextMenu = new TraceContextMenu(dbgController); traceContextMenu.setItems(dbgController.getTraceLogs()); traceContextMenu .setSelectedItems(tracesBox.getSelectionModel().getSelectedItems()); tracesBox.setContextMenu(traceContextMenu); tracesBox.selectionModelProperty().get().setSelectionMode(SelectionMode.MULTIPLE); } private void onTraceClicked(MouseEvent me) { if(me.getButton().equals(MouseButton.PRIMARY) && me.getClickCount() == 2) { TraceLog selectedItem = tracesBox.getSelectionModel().getSelectedItem(); if(selectedItem != null && selectedItem != null) { showTraceTermView(selectedItem); } } } private TermTreeView newTermTreeView() { TermTreeView termTreeView; termTreeView = new TermTreeView(); termTreeView.setMaxHeight(Integer.MAX_VALUE); VBox.setVgrow(termTreeView, Priority.ALWAYS); return termTreeView; } private void showTraceTermView(final TraceLog traceLog) { OtpErlangObject args = traceLog.getArgsList(); OtpErlangObject result = traceLog.getResultFromMap(); TermTreeView resultTermsTreeView, argTermsTreeView; resultTermsTreeView = newTermTreeView(); if(result != null) { resultTermsTreeView.populateFromTerm(traceLog.getResultFromMap()); } else { WeakChangeListener<Boolean> listener = new WeakChangeListener<Boolean>((o, oldV, newV) -> { if(newV) resultTermsTreeView.populateFromTerm(traceLog.getResultFromMap()); }); traceLog.isCompleteProperty().addListener(listener); } argTermsTreeView = newTermTreeView(); argTermsTreeView.populateFromListContents((OtpErlangList)args); SplitPane splitPane, splitPaneH; splitPane = new SplitPane(); splitPane.setOrientation(Orientation.HORIZONTAL); splitPane.getItems().addAll( labelledTreeView("Function arguments", argTermsTreeView), labelledTreeView("Result", resultTermsTreeView) ); StackTraceView stackTraceView; stackTraceView = new StackTraceView(); stackTraceView.populateFromMfaList(traceLog.getStackTrace()); String stackTraceTitle = "Stack Trace (" + traceLog.getStackTrace().arity() + ")"; TitledPane titledPane = new TitledPane(stackTraceTitle, stackTraceView); titledPane.setExpanded(!stackTraceView.isStackTracesEmpty()); splitPaneH = new SplitPane(); splitPaneH.setOrientation(Orientation.VERTICAL); splitPaneH.getItems().addAll(splitPane, titledPane); StringBuilder sb = new StringBuilder(traceLog.getPidString()); sb.append(" "); traceLog.appendModFuncArity(sb); ErlyBerly.showPane(sb.toString(), ErlyBerly.wrapInPane(splitPaneH)); } private Node labelledTreeView(String label, TermTreeView node) { return new VBox(new Label(label), node); } private void onTraceFilterChange(String searchText) { BasicSearch basicSearch = new BasicSearch(searchText); filteredTraces.setPredicate((t) -> { String logText = t.toString(); return basicSearch.matches(logText); }); } private Region traceLogFloatySearchControl() { FxmlLoadable loader = new FxmlLoadable("/floatyfield/floaty-field.fxml"); loader.load(); FloatyFieldView ffView; ffView = (FloatyFieldView) loader.controller; ffView.promptTextProperty().set("Filter trace logs"); HBox.setHgrow(loader.fxmlNode, Priority.ALWAYS); ffView.textProperty().addListener((o, ov, nv) -> { onTraceFilterChange(nv); }); Region fxmlNode = (Region) loader.fxmlNode; fxmlNode.setPadding(new Insets(5, 5, 0, 5)); Platform.runLater(() -> { FilterFocusManager.addFilter((Control) loader.fxmlNode.getChildrenUnmodifiable().get(1), 2); }); return fxmlNode; } }