From 65e4bc5caf9349e77cb646789278f8fc0bfcc9a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Ladst=C3=A4tter?= Date: Fri, 9 Aug 2024 23:11:32 +0200 Subject: [PATCH] #43: adds new toolbar and sliders to filter based on the timestamp of a log entry --- .../logorrr/model/LogEntryInstantFormat.scala | 12 ++-- .../views/logfiletab/LogFileTabContent.scala | 13 ++-- .../app/logorrr/views/ops/OpsRegion.scala | 18 ++--- .../app/logorrr/views/ops/StdOpsToolBar.scala | 15 ++++ .../views/ops/time/TimeOpsToolBar.scala | 71 +++++++++++++++++++ .../logorrr/views/ops/time/TimerSlider.scala | 52 ++++++++++++++ .../time}/TimestampSettingsButton.scala | 2 +- .../app/logorrr/views/search/OpsToolBar.scala | 13 +--- 8 files changed, 162 insertions(+), 34 deletions(-) create mode 100644 app/src/main/scala/app/logorrr/views/ops/StdOpsToolBar.scala create mode 100644 app/src/main/scala/app/logorrr/views/ops/time/TimeOpsToolBar.scala create mode 100644 app/src/main/scala/app/logorrr/views/ops/time/TimerSlider.scala rename app/src/main/scala/app/logorrr/views/{search => ops/time}/TimestampSettingsButton.scala (98%) diff --git a/app/src/main/scala/app/logorrr/model/LogEntryInstantFormat.scala b/app/src/main/scala/app/logorrr/model/LogEntryInstantFormat.scala index d08e0e8f..89ef1481 100644 --- a/app/src/main/scala/app/logorrr/model/LogEntryInstantFormat.scala +++ b/app/src/main/scala/app/logorrr/model/LogEntryInstantFormat.scala @@ -23,14 +23,14 @@ object LogEntryInstantFormat extends CanLog { val dateTimeAsString = line.substring(entrySetting.startCol, entrySetting.endCol) val dtf: DateTimeFormatter = entrySetting.dateTimeFormatter Try { - LocalDateTime.parse(dateTimeAsString, dtf).toInstant(ZoneOffset.of(entrySetting.zoneOffset)) + LocalDateTime.parse(dateTimeAsString, dtf).atZone(ZoneId.systemDefault).toInstant } match { case Success(value) => Option(value) case Failure(_) => // retrying with localtime as fallback for entries which don't have any // date information (for example: '08:34:33' representing today morning) Try { - LocalDateTime.of(LocalDate.now(), LocalTime.parse(dateTimeAsString, dtf)).toInstant(ZoneOffset.of(entrySetting.zoneOffset)) + LocalDateTime.of(LocalDate.now(), LocalTime.parse(dateTimeAsString, dtf)).atZone(ZoneId.systemDefault()).toInstant } match { case Success(value) => Option(value) case Failure(exception) => @@ -45,12 +45,8 @@ object LogEntryInstantFormat extends CanLog { } -case class LogEntryInstantFormat(range: SimpleRange - , dateTimePattern: String - , zoneOffset: String = "+1") { +case class LogEntryInstantFormat(range: SimpleRange, dateTimePattern: String) { val startCol: Int = range.start val endCol: Int = range.end - val dateTimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern(dateTimePattern).withZone(ZoneId.of(zoneOffset)) - - + val dateTimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern(dateTimePattern).withZone(ZoneId.systemDefault) } \ No newline at end of file diff --git a/app/src/main/scala/app/logorrr/views/logfiletab/LogFileTabContent.scala b/app/src/main/scala/app/logorrr/views/logfiletab/LogFileTabContent.scala index fb727fde..74dc5b05 100644 --- a/app/src/main/scala/app/logorrr/views/logfiletab/LogFileTabContent.scala +++ b/app/src/main/scala/app/logorrr/views/logfiletab/LogFileTabContent.scala @@ -5,6 +5,7 @@ import app.logorrr.io.FileId import app.logorrr.model.LogEntry import app.logorrr.views.block.ChunkListView import app.logorrr.views.ops.OpsRegion +import app.logorrr.views.ops.time.TimeOpsToolBar import app.logorrr.views.search.{Filter, FiltersToolBar, OpsToolBar} import app.logorrr.views.text.LogTextView import javafx.beans.{InvalidationListener, Observable} @@ -30,14 +31,12 @@ class LogFileTabContent(fileId: FileId // graphical display to the left private val chunkListView = ChunkListView(filteredList, mutLogFileSettings, logTextView.scrollToItem) - private val blockSizeSlider = { val bs = new BlockSizeSlider(mutLogFileSettings.getFileId) bs.valueProperty().bindBidirectional(mutLogFileSettings.blockSizeProperty) bs } - private val blockPane = { val bBp = new BorderPane(chunkListView, blockSizeSlider, null, null, null) // vBox.setStyle("-fx-background-color: #b6ff7a;") @@ -62,16 +61,18 @@ class LogFileTabContent(fileId: FileId , addFilter , entries , filteredList - , mutLogFileSettings.blockSizeProperty - , chunkListView) + , mutLogFileSettings.blockSizeProperty) private val filtersToolBar = { val fbtb = new FiltersToolBar(fileId, filteredList, removeFilter) fbtb.filtersProperty.bind(mutLogFileSettings.filtersProperty) fbtb } - - private val opsRegion: OpsRegion = new OpsRegion(opsToolBar, filtersToolBar) + val timeOpsToolBar = new TimeOpsToolBar(mutLogFileSettings + , chunkListView + , entries // we write on this list potentially + , filteredList) + private val opsRegion: OpsRegion = new OpsRegion(opsToolBar, filtersToolBar, timeOpsToolBar) private val pane = new SplitPane(blockPane, logTextView) diff --git a/app/src/main/scala/app/logorrr/views/ops/OpsRegion.scala b/app/src/main/scala/app/logorrr/views/ops/OpsRegion.scala index 0a3b09b3..ef0f395b 100644 --- a/app/src/main/scala/app/logorrr/views/ops/OpsRegion.scala +++ b/app/src/main/scala/app/logorrr/views/ops/OpsRegion.scala @@ -1,20 +1,20 @@ package app.logorrr.views.ops +import app.logorrr.views.ops.time.TimeOpsToolBar import app.logorrr.views.search.{FiltersToolBar, OpsToolBar} -import javafx.geometry.Pos -import javafx.scene.layout.HBox +import javafx.scene.layout.VBox /** * Container to horizontally align search, filters and settings */ -class OpsRegion(opsToolBar: OpsToolBar, filtersToolBar: FiltersToolBar) extends HBox { - - HBox.setHgrow(filtersToolBar, javafx.scene.layout.Priority.ALWAYS) - setAlignment(Pos.CENTER_LEFT) - opsToolBar.setMaxHeight(Double.PositiveInfinity) - filtersToolBar.setMaxHeight(Double.PositiveInfinity) - getChildren.addAll(opsToolBar, filtersToolBar) +class OpsRegion(opsToolBar: OpsToolBar + , filtersToolBar: FiltersToolBar + , timeOpsToolBar: TimeOpsToolBar) extends VBox { + getChildren.addAll(timeOpsToolBar, new StdOpsToolBar(opsToolBar, filtersToolBar)) } + + + diff --git a/app/src/main/scala/app/logorrr/views/ops/StdOpsToolBar.scala b/app/src/main/scala/app/logorrr/views/ops/StdOpsToolBar.scala new file mode 100644 index 00000000..d9ca2320 --- /dev/null +++ b/app/src/main/scala/app/logorrr/views/ops/StdOpsToolBar.scala @@ -0,0 +1,15 @@ +package app.logorrr.views.ops + +import app.logorrr.views.search.{FiltersToolBar, OpsToolBar} +import javafx.geometry.Pos +import javafx.scene.layout.HBox + +class StdOpsToolBar(opsToolBar: OpsToolBar, filtersToolBar: FiltersToolBar) extends HBox { + + HBox.setHgrow(filtersToolBar, javafx.scene.layout.Priority.ALWAYS) + setAlignment(Pos.CENTER_LEFT) + opsToolBar.setMaxHeight(Double.PositiveInfinity) + filtersToolBar.setMaxHeight(Double.PositiveInfinity) + getChildren.addAll(opsToolBar, filtersToolBar) + +} diff --git a/app/src/main/scala/app/logorrr/views/ops/time/TimeOpsToolBar.scala b/app/src/main/scala/app/logorrr/views/ops/time/TimeOpsToolBar.scala new file mode 100644 index 00000000..b3084fd2 --- /dev/null +++ b/app/src/main/scala/app/logorrr/views/ops/time/TimeOpsToolBar.scala @@ -0,0 +1,71 @@ +package app.logorrr.views.ops.time + +import app.logorrr.conf.LogoRRRGlobals +import app.logorrr.conf.mut.MutLogFileSettings +import app.logorrr.io.FileId +import app.logorrr.model.LogEntry +import app.logorrr.views.block.ChunkListView +import javafx.collections.ObservableList +import javafx.collections.transformation.FilteredList +import javafx.scene.control.{Label, ToolBar} + +import java.time.format.DateTimeFormatter + +object TimeOpsToolBar { + + private def updateFilteredList(filteredList: FilteredList[LogEntry], lower: Long, higher: Long): Unit = { + filteredList.setPredicate((t: LogEntry) => { + t.someInstant match { + case Some(value) => + val asMilli = value.toEpochMilli + lower <= asMilli && asMilli <= higher + case None => false + } + }) + } + +} + +class TimeOpsToolBar(mutLogFileSettings: MutLogFileSettings + , chunkListView: ChunkListView + , logEntries: ObservableList[LogEntry] + , filteredList: FilteredList[LogEntry]) extends ToolBar { + + val formatter: DateTimeFormatter = mutLogFileSettings.someLogEntrySettingsProperty.get().map(_.dateTimeFormatter).getOrElse(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")) + val fileId: FileId = mutLogFileSettings.getFileId + + /** + * To configure the logformat of the timestamp used in a logfile + */ + val timestampSettingsButton = new TimestampSettingsButton(LogoRRRGlobals.getLogFileSettings(fileId), chunkListView, logEntries) + + val lowSlider = new TimerSlider(filteredList) + lowSlider.setValue(lowSlider.getMin) + lowSlider.disableProperty().bind(mutLogFileSettings.hasLogEntrySettingBinding.not) + val highSlider = new TimerSlider(filteredList) + highSlider.setValue(highSlider.getMax) + highSlider.disableProperty().bind(mutLogFileSettings.hasLogEntrySettingBinding.not) + val leftLabel = new Label() + leftLabel.setText(TimerSlider.format(lowSlider.getValue.longValue(), formatter)) + leftLabel.visibleProperty().bind(mutLogFileSettings.hasLogEntrySettingBinding) + val rightLabel = new Label() + rightLabel.setText(TimerSlider.format(highSlider.getValue.longValue(), formatter)) + rightLabel.visibleProperty().bind(mutLogFileSettings.hasLogEntrySettingBinding) + lowSlider.valueProperty.addListener((_, _, newValue) => { + if (newValue.doubleValue > highSlider.getValue) lowSlider.setValue(highSlider.getValue) + leftLabel.setText(TimerSlider.format(newValue.longValue, formatter)) + TimeOpsToolBar.updateFilteredList(filteredList, newValue.longValue(), highSlider.getValue.longValue) + }) + + highSlider.valueProperty.addListener((_, _, newValue) => { + if (newValue.doubleValue < lowSlider.getValue) highSlider.setValue(lowSlider.getValue) + rightLabel.setText(TimerSlider.format(newValue.longValue, formatter)) + TimeOpsToolBar.updateFilteredList(filteredList, lowSlider.getValue.longValue, newValue.longValue()) + }) + + getItems.addAll(Seq(timestampSettingsButton, leftLabel, lowSlider, highSlider, rightLabel): _*) + +} + + + diff --git a/app/src/main/scala/app/logorrr/views/ops/time/TimerSlider.scala b/app/src/main/scala/app/logorrr/views/ops/time/TimerSlider.scala new file mode 100644 index 00000000..30b8954d --- /dev/null +++ b/app/src/main/scala/app/logorrr/views/ops/time/TimerSlider.scala @@ -0,0 +1,52 @@ +package app.logorrr.views.ops.time + +import app.logorrr.model.LogEntry +import javafx.collections.ObservableList +import javafx.scene.control.Slider + +import java.time.format.DateTimeFormatter +import java.time.{Instant, LocalDateTime, ZoneId} + +object TimerSlider { + def format(epochMilli: Long, formatter: DateTimeFormatter): String = { + val instant = Instant.ofEpochMilli(epochMilli) + val dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault) + dateTime.format(formatter) + } +} + +class TimerSlider(filteredList: ObservableList[LogEntry]) extends Slider { +/* + def onChange(c: ListChangeListener.Change[_ <: LogEntry]): Unit = { + while (c.next()) { + updateMinMax() + } + } */ + + private def updateMinMax(): Unit = { + if (2 <= filteredList.size) { + var minInstant = Instant.MAX + var maxInstant = Instant.MIN + filteredList.forEach((e: LogEntry) => { + e.someInstant match { + case Some(instant) => + if (instant.isBefore(minInstant)) { + minInstant = instant + } + if (instant.isAfter(maxInstant)) { + maxInstant = instant + } + case None => + } + }) + setMin(minInstant.toEpochMilli.toDouble) + setMax(maxInstant.toEpochMilli.toDouble) + } + } + + setPrefWidth(500) + updateMinMax() + // filteredList.addListener(JfxUtils.mkListChangeListener(onChange)) + + +} \ No newline at end of file diff --git a/app/src/main/scala/app/logorrr/views/search/TimestampSettingsButton.scala b/app/src/main/scala/app/logorrr/views/ops/time/TimestampSettingsButton.scala similarity index 98% rename from app/src/main/scala/app/logorrr/views/search/TimestampSettingsButton.scala rename to app/src/main/scala/app/logorrr/views/ops/time/TimestampSettingsButton.scala index 3d71afee..918e752a 100644 --- a/app/src/main/scala/app/logorrr/views/search/TimestampSettingsButton.scala +++ b/app/src/main/scala/app/logorrr/views/ops/time/TimestampSettingsButton.scala @@ -1,4 +1,4 @@ -package app.logorrr.views.search +package app.logorrr.views.ops.time import app.logorrr.conf.mut.MutLogFileSettings import app.logorrr.model.LogEntry diff --git a/app/src/main/scala/app/logorrr/views/search/OpsToolBar.scala b/app/src/main/scala/app/logorrr/views/search/OpsToolBar.scala index 18a627f6..2e531f3e 100644 --- a/app/src/main/scala/app/logorrr/views/search/OpsToolBar.scala +++ b/app/src/main/scala/app/logorrr/views/search/OpsToolBar.scala @@ -1,11 +1,10 @@ package app.logorrr.views.search -import app.logorrr.conf.LogoRRRGlobals import app.logorrr.io.FileId import app.logorrr.model.LogEntry import app.logorrr.util.OsUtil import app.logorrr.views.autoscroll.AutoScrollCheckBox -import app.logorrr.views.block.{ChunkListView, HasBlockSizeProperty} +import app.logorrr.views.block.HasBlockSizeProperty import app.logorrr.views.ops.{ClearLogButton, CopyLogButton, DecreaseBlockSizeButton, IncreaseBlockSizeButton} import app.logorrr.views.text.toolbaractions.{DecreaseTextSizeButton, IncreaseTextSizeButton} import javafx.beans.property.SimpleIntegerProperty @@ -18,7 +17,6 @@ import javafx.scene.input.{KeyCode, KeyEvent} object OpsToolBar { - /** increment / decrement font size */ val fontSizeStep: Int = 1 @@ -33,8 +31,7 @@ class OpsToolBar(fileId: FileId , addFilterFn: Filter => Unit , logEntries: ObservableList[LogEntry] , filteredList: FilteredList[LogEntry] - , val blockSizeProperty: SimpleIntegerProperty - , chunkListView: ChunkListView) + , val blockSizeProperty: SimpleIntegerProperty) extends ToolBar with HasBlockSizeProperty { @@ -64,10 +61,6 @@ class OpsToolBar(fileId: FileId private val copySelectionButton = new CopyLogButton(fileId, filteredList) - /** - * To configure the logformat of the timestamp used in a logfile - */ - val timestampSettingsButton = new TimestampSettingsButton(LogoRRRGlobals.getLogFileSettings(fileId), chunkListView, logEntries) def execSearchOnHitEnter(event: KeyEvent): Unit = { if (event.getCode == KeyCode.ENTER) { @@ -89,7 +82,7 @@ class OpsToolBar(fileId: FileId } val otherItems: Seq[Node] = { - Seq(autoScrollCheckBox, clearLogButton, copySelectionButton, timestampSettingsButton) + Seq(autoScrollCheckBox, clearLogButton, copySelectionButton) } getItems.addAll(searchItems ++ sizeItems ++ otherItems: _*)