diff --git a/app/src/main/scala/app/logorrr/conf/SettingsIO.scala b/app/src/main/scala/app/logorrr/conf/SettingsIO.scala index d67a5bc4..89f2473b 100644 --- a/app/src/main/scala/app/logorrr/conf/SettingsIO.scala +++ b/app/src/main/scala/app/logorrr/conf/SettingsIO.scala @@ -26,6 +26,19 @@ object SettingsIO extends CanLog { SettingsIO.write(settings.copy(recentFiles = updateRecentFilesFn(settings.recentFiles))) } + def updateDividerPosition(path: Path, dividerPosition: Double): Unit = { + val settings = read() + val defs: Seq[LogReportDefinition] = for (lrd <- settings.recentFiles.logReportDefinitions) yield { + if (lrd.pathAsString == path.toAbsolutePath.toString) { + lrd.copy(dividerPosition = dividerPosition) + } else { + lrd + } + } + SettingsIO.write(settings.copy(recentFiles = settings.recentFiles.copy(logReportDefinitions = defs))) + } + + def updateActiveLogFile(path: Path): Unit = { val settings = read() val defs: Seq[LogReportDefinition] = for (lrd <- settings.recentFiles.logReportDefinitions) yield { diff --git a/app/src/main/scala/app/logorrr/model/LogReport.scala b/app/src/main/scala/app/logorrr/model/LogReport.scala index 1661c74d..ccd8fa54 100644 --- a/app/src/main/scala/app/logorrr/model/LogReport.scala +++ b/app/src/main/scala/app/logorrr/model/LogReport.scala @@ -36,7 +36,9 @@ object LogReport extends CanLog { new LogReport(logFile , FXCollections.observableList(logEntryStream.collect(Collectors.toList[LogEntry]())) , filters - , someColumnDef) + , someColumnDef + , lrd.active + , lrd.dividerPosition) }, s"Imported ${lrd.path.toAbsolutePath.toString} ... ") private def readFromFile(logFile: Path): util.List[String] = { @@ -66,16 +68,18 @@ object LogReport extends CanLog { case class LogReport(path: Path , entries: ObservableList[LogEntry] , filters: Seq[Filter] - , someColumnDef: Option[LogColumnDef]) { + , someColumnDef: Option[LogColumnDef] + , active: Boolean + , dividerPosition: Double) { - val logFileDefinition: LogReportDefinition = LogReportDefinition(path.toAbsolutePath.toString, someColumnDef, false, filters) + val logFileDefinition: LogReportDefinition = LogReportDefinition(path.toAbsolutePath.toString, someColumnDef, active, dividerPosition, filters) val lengthProperty = new SimpleIntegerProperty(entries.size()) val titleProperty = new SimpleStringProperty(computeTabName) val tailer = new Tailer(path.toFile, new LTailerListener(entries), 1000, true) /** start observing log file for changes */ - def start(): Unit = new Thread(tailer).start() + def init(): Unit = new Thread(tailer).start() /** stop observing changes */ def stop(): Unit = tailer.stop() diff --git a/app/src/main/scala/app/logorrr/model/LogReportDefinition.scala b/app/src/main/scala/app/logorrr/model/LogReportDefinition.scala index b974810f..73700e97 100644 --- a/app/src/main/scala/app/logorrr/model/LogReportDefinition.scala +++ b/app/src/main/scala/app/logorrr/model/LogReportDefinition.scala @@ -10,11 +10,13 @@ object LogReportDefinition { implicit lazy val reader = deriveReader[LogReportDefinition] implicit lazy val writer = deriveWriter[LogReportDefinition] + val defaultDividerPosition = 0.5 + val defaultActive = false - def apply(p: Path): LogReportDefinition = LogReportDefinition(p.toAbsolutePath.toString, None, false, Filter.seq) + def apply(p: Path): LogReportDefinition = LogReportDefinition(p.toAbsolutePath.toString, None, defaultActive, defaultDividerPosition, Filter.seq) def apply(p: Path, logColumnDef: LogColumnDef): LogReportDefinition = - LogReportDefinition(p.toAbsolutePath.toString, Option(logColumnDef), false, Filter.seq) + LogReportDefinition(p.toAbsolutePath.toString, Option(logColumnDef), defaultActive, defaultDividerPosition, Filter.seq) } @@ -33,6 +35,7 @@ object LogReportDefinition { case class LogReportDefinition(pathAsString: String , someColumnDefinition: Option[LogColumnDef] = None , active: Boolean + , dividerPosition: Double , filters: Seq[Filter]) { val path: Path = Paths.get(pathAsString) diff --git a/app/src/main/scala/app/logorrr/views/LogReportTab.scala b/app/src/main/scala/app/logorrr/views/LogReportTab.scala index a9a92f91..6772c336 100644 --- a/app/src/main/scala/app/logorrr/views/LogReportTab.scala +++ b/app/src/main/scala/app/logorrr/views/LogReportTab.scala @@ -5,7 +5,7 @@ import app.logorrr.model.{LogEntry, LogReport} import app.logorrr.util.{CanLog, CollectionUtils, JfxUtils, LogoRRRFonts} import app.logorrr.views.visual.LogVisualView import javafx.beans.property.{SimpleBooleanProperty, SimpleIntegerProperty, SimpleListProperty, SimpleObjectProperty} -import javafx.beans.value.{ChangeListener, ObservableValue} +import javafx.beans.value.ChangeListener import javafx.beans.{InvalidationListener, Observable} import javafx.collections.transformation.FilteredList import javafx.scene.control._ @@ -21,12 +21,13 @@ object LogReportTab { val lv = new LogReportTab(logReport , logViewTabPane.sceneWidthProperty.get() , logViewTabPane.squareWidthProperty.get() + , logReport.logFileDefinition.dividerPosition , initFileMenu) logReport.filters.foreach(lv.addFilter) /** activate invalidation listener on filtered list */ - lv.start() + lv.init() lv.sceneWidthProperty.bind(logViewTabPane.sceneWidthProperty) lv.squareWidthProperty.bind(logViewTabPane.squareWidthProperty) lv @@ -44,6 +45,7 @@ object LogReportTab { class LogReportTab(val logReport: LogReport , val initialSceneWidth: Int , val initialSquareWidth: Int + , val initialDividerPosition: Double , initFileMenu: => Unit) extends Tab with CanLog { @@ -53,54 +55,15 @@ class LogReportTab(val logReport: LogReport /** repaint if entries or filters change */ val repaintInvalidationListener: InvalidationListener = (_: Observable) => repaint() - def start(): Unit = { - logReport.start() - installInvalidationListener() - } - - /** don't monitor file anymore if tab is closed, free invalidation listeners */ - setOnClosed(_ => closeTab()) - - /** - * Actions to perform if tab is closed: - * - * - end monitoring of file - * - update config file - * - update file menu - * - */ - def closeTab(): Unit = { - SettingsIO.updateRecentFileSettings(rf => { - val filteredFiles = rf.logReportDefinitions.filterNot(s => s.pathAsString == logReport.logFileDefinition.path.toAbsolutePath.toString) - rf.copy(logReportDefinitions = filteredFiles) - }) - initFileMenu - shutdown() - } - - def shutdown(): Unit = { - logInfo(s"Closing file ${logReport.path.toAbsolutePath} ...") - uninstallInvalidationListener() - logReport.stop() - } - - textProperty.bind(logReport.titleProperty) - /** list of search filters to be applied to a Log Report */ val filtersListProperty = new SimpleListProperty[Filter](CollectionUtils.mkEmptyObservableList()) /** bound to sceneWidthProperty of parent LogViewTabPane */ val sceneWidthProperty = new SimpleIntegerProperty(initialSceneWidth) - def sceneWidth = sceneWidthProperty.get() - /** bound to squareWidthProperty of parent LogViewTabPane */ val squareWidthProperty = new SimpleIntegerProperty(initialSquareWidth) - def getSquareWidth = squareWidthProperty.get() - - val InitialRatio = 0.5 - /** top component for log view */ val borderPane = new BorderPane() @@ -110,20 +73,8 @@ class LogReportTab(val logReport: LogReport /** list which holds all entries, default to display all (can be changed via buttons) */ val filteredList = new FilteredList[LogEntry](logReport.entries) - def installInvalidationListener(): Unit = { - // to detect when we apply a new filter via filter buttons (see FilterButtonsToolbar) - filteredList.predicateProperty().addListener(repaintInvalidationListener) - logReport.entries.addListener(repaintInvalidationListener) - // if application changes width this will trigger repaint (See Issue #9) - splitPane.widthProperty().addListener(repaintInvalidationListener) - } - - def uninstallInvalidationListener(): Unit = { - filteredList.predicateProperty().removeListener(repaintInvalidationListener) - logReport.entries.removeListener(repaintInvalidationListener) - } - private val opsToolBar = new OpsToolBar(this) + private val filterButtonsToolBar = { val fbtb = new FilterButtonsToolBar(this, filteredList, logReport.entries.size) fbtb.filtersProperty.bind(filtersListProperty) @@ -136,7 +87,7 @@ class LogReportTab(val logReport: LogReport vb } - val initialWidth = (sceneWidth * InitialRatio).toInt + val initialWidth = (sceneWidth * initialDividerPosition).toInt private lazy val logVisualView = { val lvv = new LogVisualView(filteredList.asScala, initialWidth, getSquareWidth) @@ -153,29 +104,98 @@ class LogReportTab(val logReport: LogReport l } - borderPane.setTop(opsToolBox) - borderPane.setCenter(splitPane) - borderPane.setBottom(entryLabel) - setContent(borderPane) + /** to share state between visual view and text view. index can be selected by navigation in visual view */ val selectedIndexProperty = new SimpleIntegerProperty() - selectedIndexProperty.bind(logVisualView.selectedIndexProperty) + val selectedEntryProperty = new SimpleObjectProperty[LogEntry]() - selectedIndexProperty.addListener(JfxUtils.onNew[Number](selectEntry)) + private val logEntryChangeListener: ChangeListener[LogEntry] = JfxUtils.onNew[LogEntry](updateEntryLabel) - val selectedEntryProperty = new SimpleObjectProperty[LogEntry]() + def init(): Unit = { + borderPane.setTop(opsToolBox) + borderPane.setCenter(splitPane) + borderPane.setBottom(entryLabel) - selectedEntryProperty.bindBidirectional(logVisualView.selectedEntryProperty) + setContent(borderPane) + /** don't monitor file anymore if tab is closed, free invalidation listeners */ + setOnClosed(_ => closeTab()) - private val logEntryChangeListener: ChangeListener[LogEntry] = JfxUtils.onNew[LogEntry](updateEntryLabel) + textProperty.bind(logReport.titleProperty) + + selectedIndexProperty.bind(logVisualView.selectedIndexProperty) + + selectedIndexProperty.addListener(JfxUtils.onNew[Number](selectEntry)) - selectedEntryProperty.addListener(logEntryChangeListener) - // if user changes selected item in listview, change footer as well - logTextView.listView.getSelectionModel.selectedItemProperty.addListener(logEntryChangeListener) + selectedEntryProperty.bindBidirectional(logVisualView.selectedEntryProperty) + + selectedEntryProperty.addListener(logEntryChangeListener) + + // if user changes selected item in listview, change footer as well + logTextView.listView.getSelectionModel.selectedItemProperty.addListener(logEntryChangeListener) + + splitPane.getItems.addAll(logVisualView, logTextView) + + + /** + * we are interested just in the first divider. If it changes its position (which means the user interacts) then + * update logVisualView + * */ + splitPane.getDividers.get(0).positionProperty().addListener(JfxUtils.onNew { + t1: Number => + val width = t1.doubleValue() * splitPane.getWidth + SettingsIO.updateDividerPosition(logReport.path, t1.doubleValue()) + repaint(width) + }) + + logReport.init() + + setDivider(initialDividerPosition) + installInvalidationListener() + } + + /** + * Actions to perform if tab is closed: + * + * - end monitoring of file + * - update config file + * - update file menu + * + */ + def closeTab(): Unit = { + SettingsIO.updateRecentFileSettings(rf => { + val filteredFiles = rf.logReportDefinitions.filterNot(s => s.pathAsString == logReport.logFileDefinition.path.toAbsolutePath.toString) + rf.copy(logReportDefinitions = filteredFiles) + }) + initFileMenu + shutdown() + } + + def shutdown(): Unit = { + logInfo(s"Closing file ${logReport.path.toAbsolutePath} ...") + uninstallInvalidationListener() + logReport.stop() + } + + def sceneWidth = sceneWidthProperty.get() + + def getSquareWidth = squareWidthProperty.get() + + def installInvalidationListener(): Unit = { + // to detect when we apply a new filter via filter buttons (see FilterButtonsToolbar) + filteredList.predicateProperty().addListener(repaintInvalidationListener) + logReport.entries.addListener(repaintInvalidationListener) + // if application changes width this will trigger repaint (See Issue #9) + splitPane.widthProperty().addListener(repaintInvalidationListener) + } + + def uninstallInvalidationListener(): Unit = { + filteredList.predicateProperty().removeListener(repaintInvalidationListener) + logReport.entries.removeListener(repaintInvalidationListener) + } def selectEntry(number: Number): Unit = logTextView.selectEntryByIndex(number.intValue) @@ -192,18 +212,7 @@ class LogReportTab(val logReport: LogReport } } - splitPane.getItems.addAll(logVisualView, logTextView) - - /** - * we are interested just in the first divider. If it changes its position (which means the user interacts) then - * update logVisualView - * */ - splitPane.getDividers.get(0).positionProperty().addListener(new ChangeListener[Number] { - override def changed(observableValue: ObservableValue[_ <: Number], t: Number, t1: Number): Unit = { - val width = t1.doubleValue() * splitPane.getWidth - repaint(width) - } - }) + def setDivider(pos: Double): Unit = splitPane.getDividers.get(0).setPosition(pos) def addFilter(filter: Filter): Unit = filtersListProperty.add(filter) diff --git a/app/src/main/scala/app/logorrr/views/LogViewTabPane.scala b/app/src/main/scala/app/logorrr/views/LogViewTabPane.scala index a21f7421..48e853fd 100644 --- a/app/src/main/scala/app/logorrr/views/LogViewTabPane.scala +++ b/app/src/main/scala/app/logorrr/views/LogViewTabPane.scala @@ -62,7 +62,10 @@ class LogViewTabPane(initFileMenu: => Unit) }) } - def add(logReport: LogReport): Unit = getTabs.add(LogReportTab(this, logReport, initFileMenu)) + def add(logReport: LogReport): Unit = { + val tab = LogReportTab(this, logReport, initFileMenu) + getTabs.add(tab) + } def contains(p: Path): Boolean = getLogReportTabs.exists(lr => lr.logReport.path.toAbsolutePath.toString == p.toAbsolutePath.toString) diff --git a/app/src/main/scala/app/logorrr/views/main/AppMainBorderPane.scala b/app/src/main/scala/app/logorrr/views/main/AppMainBorderPane.scala index 07e3451f..6687535b 100644 --- a/app/src/main/scala/app/logorrr/views/main/AppMainBorderPane.scala +++ b/app/src/main/scala/app/logorrr/views/main/AppMainBorderPane.scala @@ -52,11 +52,16 @@ class AppMainBorderPane(initialSceneWidth: Int val path = f.toPath if (Files.exists(path)) { if (!contains(path)) { - val logFileDefinition = LogReportDefinition(path.toAbsolutePath.toString, None, true, Filter.seq) + val logFileDefinition = + LogReportDefinition(path.toAbsolutePath.toString + , None + , active = true + , LogReportDefinition.defaultDividerPosition + , Filter.seq) SettingsIO.updateRecentFileSettings(rf => rf.copy(logReportDefinitions = logFileDefinition +: rf.logReportDefinitions)) reInitMenuBarFn addLogReport(logFileDefinition) - logViewTabPane.selectLog(path) + selectLog(path) } else { logWarn(s"${path.toAbsolutePath.toString} is already opened ...") } diff --git a/app/src/main/scala/app/logorrr/views/main/LogoRRRMain.scala b/app/src/main/scala/app/logorrr/views/main/LogoRRRMain.scala index e58f55d6..23b9ae2d 100644 --- a/app/src/main/scala/app/logorrr/views/main/LogoRRRMain.scala +++ b/app/src/main/scala/app/logorrr/views/main/LogoRRRMain.scala @@ -38,7 +38,9 @@ class LogoRRRMain(hostServices: HostServices // the last one settings.recentFiles.someActive match { case Some(value) => selectLog(value.path) - case None => selectLastLogReport() + case None => + logError("No active log file entries found.") + selectLastLogReport() } // only after having initialized we activate change listeners */ ambp.init() @@ -49,7 +51,7 @@ class LogoRRRMain(hostServices: HostServices logTrace(s"Try to open log file ${path.toAbsolutePath.toString}") if (!ambp.contains(path)) { - SettingsIO.updateRecentFileSettings(rf => rf.copy(logReportDefinitions = LogReportDefinition(path.toString, None, true, Filter.seq) +: rf.logReportDefinitions)) + SettingsIO.updateRecentFileSettings(rf => rf.copy(logReportDefinitions = LogReportDefinition(path.toString, None, true, LogReportDefinition.defaultDividerPosition, Filter.seq) +: rf.logReportDefinitions)) addLogReport(LogReportDefinition(path)) selectLog(path) initFileMenu()