All Downloads are FREE. Search and download functionalities are using the official Maven repository.

us.ihmc.scs2.sessionVisualizer.jfx.session.mcap.MCAPConsoleLogOutputPaneController Maven / Gradle / Ivy

package us.ihmc.scs2.sessionVisualizer.jfx.session.mcap;

import javafx.beans.binding.DoubleBinding;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import org.apache.commons.lang3.mutable.MutableLong;
import us.ihmc.scs2.session.mcap.MCAPConsoleLogManager.MCAPConsoleLogItem;
import us.ihmc.scs2.session.mcap.MCAPConsoleLogManager.MCAPLogLevel;
import us.ihmc.scs2.session.mcap.MCAPLogFileReader;
import us.ihmc.scs2.session.mcap.MCAPLogSession;
import us.ihmc.scs2.sessionVisualizer.jfx.managers.SessionVisualizerToolkit;
import us.ihmc.scs2.sessionVisualizer.jfx.tools.JavaFXMissingTools;
import us.ihmc.scs2.sessionVisualizer.jfx.tools.MenuTools;
import us.ihmc.scs2.sessionVisualizer.jfx.tools.ObservedAnimationTimer;
import us.ihmc.scs2.sharedMemory.interfaces.YoBufferPropertiesReadOnly;
import us.ihmc.scs2.simulation.SpyList;
import us.ihmc.yoVariables.listener.YoVariableChangedListener;
import us.ihmc.yoVariables.variable.YoLong;

import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;

public class MCAPConsoleLogOutputPaneController
{
   private static final int MAX_UPDATES_PER_FRAME = 10;

   @FXML
   private Pane mainPane;
   @FXML
   private ListView consoleOutputListView;

   private EventHandler goToLogEventMouseEventHandler;
   private Consumer scrollLastLogItemListener;
   private MCAPLogSession session;

   private final ConcurrentLinkedQueue listCellsToUpdate = new ConcurrentLinkedQueue<>();

   private final ObservedAnimationTimer observedAnimationTimer = new ObservedAnimationTimer()
   {

      @Override
      public void handleImpl(long now)
      {
         MCAPConsoleLogItemListCell cell;
         for (int i = 0; i < MAX_UPDATES_PER_FRAME; i++)
         {
            if ((cell = listCellsToUpdate.poll()) == null)
               break;
            cell.updateTextFill();
         }
      }
   };

   public void initialize(SessionVisualizerToolkit toolkit)
   {
      MenuTools.setupContextMenu(consoleOutputListView, mcapConsoleLogItemListView ->
      {
         MenuItem goToLogItem = new MenuItem("Go to Log Item (Double Click)");
         goToLogItem.setOnAction(event ->
                                 {
                                    MCAPConsoleLogItem selectedLogItem = mcapConsoleLogItemListView.getSelectionModel().getSelectedItem();
                                    if (selectedLogItem != null)
                                       gotToLogItem(session, selectedLogItem);
                                 });
         return goToLogItem;
      }, mcapConsoleLogItemListView ->
                                 {
                                    MenuItem copyLogItem = new MenuItem("Copy Log Item (Ctrl+C)");
                                    copyLogItem.setOnAction(event -> copySelectedLogItemsToClipboard(mcapConsoleLogItemListView));
                                    return copyLogItem;
                                 });

      consoleOutputListView.setOnKeyPressed(event ->
                                            {
                                               if (event.getCode().equals(KeyCode.C) && event.isControlDown())
                                               {
                                                  copySelectedLogItemsToClipboard(consoleOutputListView);
                                                  event.consume();
                                               }
                                            });
   }

   public void startSession(MCAPLogSession session)
   {
      this.session = session;
      // Setup the console output
      MCAPLogFileReader mcapLogFileReader = session.getMCAPLogFileReader();
      consoleOutputListView.setCellFactory(param -> new MCAPConsoleLogItemListCell(param, session, mcapLogFileReader.getCurrentTimestamp()));
      consoleOutputListView.getItems().clear();
      SpyList sessionLogItems = mcapLogFileReader.getConsoleLogManager().getAllConsoleLogItems();
      consoleOutputListView.getItems().setAll(sessionLogItems);
      sessionLogItems.addListener((change) ->
                                  {
                                     if (change.wasAdded())
                                     {
                                        // Doing that outside the JavaFX thread to ensure we have the right size.
                                        int newItemsSize = sessionLogItems.size();
                                        JavaFXMissingTools.runLater(getClass(), () ->
                                        {
                                           // Log items only get added, so we can just add the new items.
                                           // This manner is robust to concurrent modifications and missing an update.
                                           for (int i = consoleOutputListView.getItems().size(); i < newItemsSize; i++)
                                           {
                                              consoleOutputListView.getItems().add(sessionLogItems.get(i));
                                           }
                                        });
                                     }
                                  });

      consoleOutputListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

      MutableLong requestingLogTimestamp = new MutableLong(Long.MIN_VALUE);

      goToLogEventMouseEventHandler = event ->
      {
         if (event.getButton() != MouseButton.PRIMARY)
            return;
         if (event.getClickCount() != 2)
            return;
         if (!event.isStillSincePress())
            return;

         MCAPConsoleLogItem selectedLogItem = consoleOutputListView.getSelectionModel().getSelectedItem();
         if (selectedLogItem == null)
            return;
         requestingLogTimestamp.setValue(selectedLogItem.logTime());

         gotToLogItem(session, selectedLogItem);
      };
      scrollLastLogItemListener = new Consumer<>()
      {
         private long lastTimestamp = Long.MIN_VALUE;
         private int lastSearchResult = Integer.MIN_VALUE;

         @Override
         public void accept(YoBufferPropertiesReadOnly bufferProperties)
         {
            long currentTimestamp = session.getMCAPLogFileReader().getCurrentTimestamp().getValue();

            if (requestingLogTimestamp.getValue() != Long.MIN_VALUE && requestingLogTimestamp.getValue() == currentTimestamp)
            {
               requestingLogTimestamp.setValue(Long.MIN_VALUE);
               lastTimestamp = currentTimestamp;
               return;
            }

            if (currentTimestamp == lastTimestamp)
               return;
            lastTimestamp = currentTimestamp;

            int searchResult = Collections.binarySearch(consoleOutputListView.getItems(),
                                                        new MCAPConsoleLogItem(currentTimestamp, null, null, null, null, null, 0),
                                                        Comparator.comparingLong(MCAPConsoleLogItem::logTime));
            if (searchResult < 0)
               searchResult = -searchResult - 1;
            if (searchResult == lastSearchResult)
               return;
            lastSearchResult = searchResult;
            int finalSearchResult = Math.max(0, searchResult - 2);
            JavaFXMissingTools.runLater(MCAPConsoleLogOutputPaneController.this.getClass(), () -> consoleOutputListView.scrollTo(finalSearchResult));
         }
      };

      consoleOutputListView.addEventFilter(MouseEvent.MOUSE_CLICKED, goToLogEventMouseEventHandler);
      session.addCurrentBufferPropertiesListener(scrollLastLogItemListener);
      observedAnimationTimer.start();
   }

   private static void gotToLogItem(MCAPLogSession session, MCAPConsoleLogItem selectedLogItem)
   {
      int logIndex = session.getMCAPLogFileReader().getIndexFromTimestamp(selectedLogItem.logTime());
      session.submitLogPositionRequest(logIndex);
   }

   private static void copySelectedLogItemsToClipboard(ListView mcapConsoleLogItemListView)
   {
      ObservableList selectedItems = mcapConsoleLogItemListView.getSelectionModel().getSelectedItems();
      StringBuilder logItemString = new StringBuilder();
      for (MCAPConsoleLogItem logItem : selectedItems)
      {
         logItemString.append("[%s] [%s] [%s]: %s\n".formatted(logItem.logLevel(), logItem.instant(), logItem.processName(), logItem.message()));
      }

      ClipboardContent clipboardContent = new ClipboardContent();
      clipboardContent.putString(logItemString.toString());
      Clipboard.getSystemClipboard().setContent(clipboardContent);
   }

   public void stopSession()
   {
      consoleOutputListView.getItems().clear();
      if (goToLogEventMouseEventHandler != null)
         consoleOutputListView.removeEventFilter(MouseEvent.MOUSE_CLICKED, goToLogEventMouseEventHandler);
      goToLogEventMouseEventHandler = null;
      if (scrollLastLogItemListener != null)
         session.removeCurrentBufferPropertiesListener(scrollLastLogItemListener);
      scrollLastLogItemListener = null;
      observedAnimationTimer.stop();
      listCellsToUpdate.clear();
   }

   private class MCAPConsoleLogItemListCell extends javafx.scene.control.ListCell
   {
      private final Color defaultColor = Color.BLACK;
      private final Map logLevelToColorMap = Map.of(MCAPLogLevel.UNKNOWN,
                                                                         defaultColor,
                                                                         MCAPLogLevel.INFO,
                                                                         Color.CORNFLOWERBLUE,
                                                                         MCAPLogLevel.WARNING,
                                                                         Color.ORANGE,
                                                                         MCAPLogLevel.ERROR,
                                                                         Color.RED,
                                                                         MCAPLogLevel.FATAL,
                                                                         Color.DARKRED);

      private final Color futureColor = Color.GRAY.deriveColor(0, 1, 1, 0.5);
      private final Map logLevelToStringMap = Map.of(MCAPLogLevel.UNKNOWN,
                                                                           "  ???",
                                                                           MCAPLogLevel.DEBUG,
                                                                           "DEBUG",
                                                                           MCAPLogLevel.INFO,
                                                                           " INFO",
                                                                           MCAPLogLevel.WARNING,
                                                                           " WARN",
                                                                           MCAPLogLevel.ERROR,
                                                                           "ERROR",
                                                                           MCAPLogLevel.FATAL,
                                                                           "FATAL");
      private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS a z");
      private final ZoneId zoneId = ZoneId.systemDefault(); // Need to parameterize this.
      private final ListView owner;
      private final YoLong currentTimestamp;
      private YoVariableChangedListener timestampListener;

      public MCAPConsoleLogItemListCell(ListView owner, MCAPLogSession session, YoLong currentTimestamp)
      {
         this.owner = owner;
         this.currentTimestamp = currentTimestamp;
      }

      @Override
      protected void updateItem(MCAPConsoleLogItem item, boolean empty)
      {
         super.updateItem(item, empty);

         if (empty || item == null)
         {
            setText(null);
            setGraphic(null);
            if (timestampListener != null)
               currentTimestamp.removeListener(timestampListener);
            timestampListener = null;
         }
         else
         {
            setFont(Font.font("Monospaced", 14.0));

            DoubleBinding cellWidthProperty = owner.widthProperty().subtract(15);
            minWidthProperty().bind(cellWidthProperty);
            prefWidthProperty().bind(cellWidthProperty);
            maxWidthProperty().bind(cellWidthProperty);

            setWrapText(true);
            updateTextFill();
            timestampListener = v -> listCellsToUpdate.add(this);
            currentTimestamp.addListener(timestampListener);
            String dateTimeFormatted = dateTimeFormatter.format(item.instant().atZone(zoneId));
            String logLevelString = logLevelToStringMap.get(item.logLevel() == null ? MCAPLogLevel.UNKNOWN : item.logLevel());
            setText("[%s] [%s]\n\t[%s]: %s".formatted(logLevelString, dateTimeFormatted, item.processName(), item.message()));
            setGraphic(null);
         }
      }

      private Paint previousTextFill;

      private void updateTextFill()
      {
         MCAPConsoleLogItem item = getItem();

         if (item == null)
         {
            previousTextFill = null;
            return;
         }

         Paint newTextFill;
         if (item.logTime() > currentTimestamp.getValue())
            newTextFill = futureColor;
         else if (logLevelToColorMap.containsKey(item.logLevel()))
            newTextFill = logLevelToColorMap.get(item.logLevel());
         else
            newTextFill = logLevelToColorMap.get(MCAPLogLevel.UNKNOWN);

         if (previousTextFill != newTextFill)
         {
            setTextFill(newTextFill);
            previousTextFill = newTextFill;
         }
      }
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy