Skip to content

Commit

Permalink
#147: LogoRRR now remembers the state of a filter
Browse files Browse the repository at this point in the history
  • Loading branch information
rladstaetter committed Oct 2, 2023
1 parent 27f8a45 commit 23e91ce
Show file tree
Hide file tree
Showing 20 changed files with 128 additions and 125 deletions.
2 changes: 1 addition & 1 deletion app/src/main/java/app/logorrr/io/FileManager.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ object FileManager extends CanLog {
private def openFileWithDetectedEncoding(path: Path): BufferedReader = {
val encoding = FEncoding(path)
if (encoding == Unknown) {
logTrace(encoding.asString + " encoding - fallback to UTF-8")
logTrace(s"${encoding.asString} encoding - fallback to UTF-8")
new BufferedReader(new InputStreamReader(new FileInputStream(path.toFile), UTF8.asString))
} else {
new BufferedReader(new InputStreamReader(new FileInputStream(path.toFile), encoding.asString))
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/scala/app/logorrr/LogoRRRApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class LogoRRRApp extends javafx.application.Application with CanLog {


def start(stage: Stage): Unit = {
logInfo(s"Started " + AppMeta.fullAppNameWithVersion)
logInfo(s"Started ${AppMeta.fullAppNameWithVersion}")
logInfo(s"Working directory: ${Paths.get("").toAbsolutePath.toString}")
val settings: Settings = SettingsIO.fromFile()
LogoRRRGlobals.set(settings, getHostServices)
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/scala/app/logorrr/model/LogFileReader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ object LogFileReader extends CanLog {

def readFromFile(logFile: Path): Seq[String] = {
if (OsUtil.enableSecurityBookmarks) {
logInfo("Registering security bookmark for " + logFile.toAbsolutePath.toString)
logInfo(s"Registering security bookmark for ${logFile.toAbsolutePath.toString}")
OsxBridge.registerPath(logFile.toAbsolutePath.toString)
}
val lines = FileManager.fromPath(logFile)
Expand Down
10 changes: 5 additions & 5 deletions app/src/main/scala/app/logorrr/model/LogFileSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import app.logorrr.util.{CanLog, OsUtil}
import app.logorrr.views.search.Filter
import javafx.collections.{FXCollections, ObservableList}
import javafx.scene.paint.Color
import pureconfig.{ConfigReader, ConfigWriter}
import pureconfig.generic.semiauto.{deriveReader, deriveWriter}
import pureconfig.{ConfigReader, ConfigWriter}

import java.nio.file.{Files, Path, Paths}
import java.time.Instant
Expand All @@ -23,10 +23,10 @@ object LogFileSettings {
private val DefaultLogFormat: Option[LogEntryInstantFormat] = None
private val DefaultAutoScroll = false

private val finest: Filter = new Filter("FINEST", Color.GREY)
private val info: Filter = new Filter("INFO", Color.GREEN)
private val warning: Filter = new Filter("WARNING", Color.ORANGE)
private val severe: Filter = new Filter("SEVERE", Color.RED)
private val finest: Filter = new Filter("FINEST", Color.GREY, true)
private val info: Filter = new Filter("INFO", Color.GREEN, true)
private val warning: Filter = new Filter("WARNING", Color.ORANGE, true)
private val severe: Filter = new Filter("SEVERE", Color.RED, true)

val DefaultFilter: Seq[Filter] = Seq(finest, info, warning, severe)
val DefaultFontSize = 12
Expand Down
17 changes: 3 additions & 14 deletions app/src/main/scala/app/logorrr/views/LogFileTab.scala
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class LogFileTab(val pathAsString: String
op
}

private val filtersToolBar = {
val filtersToolBar = {
val fbtb = new FiltersToolBar(filteredList, removeFilter)
fbtb.filtersProperty.bind(filtersListProperty)
fbtb
Expand Down Expand Up @@ -259,9 +259,8 @@ class LogFileTab(val pathAsString: String
* - update file menu
*
*/
def closeTab(): Unit = {
shutdown()
}
def closeTab(): Unit = shutdown()


def shutdown(): Unit = {
if (getLogFileSettings.isAutoScroll) {
Expand All @@ -277,15 +276,5 @@ class LogFileTab(val pathAsString: String

def removeFilter(filter: Filter): Unit = filtersListProperty.remove(filter)

/*
def getVisualViewWidth(): Double = {
val w = splitPane.getDividers.get(0).getPosition * splitPane.getWidth
if (w != 0.0) {
w
} else {
initialWidth.doubleValue()
}
}
*/

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class LogoRRRMain(closeStage: => Unit)

private def loadLogFiles(logs: Seq[LogFileSettings]): Unit = {
val futures: Future[Seq[(String, ObservableList[LogEntry])]] = Future.sequence {
logInfo(s"Loading ${logs.length} log files: " + logs.map(_.pathAsString).mkString("['", "',`'", "']"))
logInfo(s"Loading ${logs.length} log files: ${logs.map(_.pathAsString).mkString("['", "',`'", "']")}")
logs.filter(s => !ambp.contains(s.pathAsString)).map(s => Future((s.pathAsString, s.readEntries())))
}
futures.onComplete({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class LogoRRRMainTabPane()

def contains(p: String): Boolean = getLogFileTabs.exists(lr => lr.pathAsString == p)

private def getLogFileTabs: mutable.Seq[LogFileTab] = getTabs.asScala.flatMap {
def getLogFileTabs: mutable.Seq[LogFileTab] = getTabs.asScala.flatMap {
t =>
t match {
case l: LogFileTab => Option(l)
Expand Down
25 changes: 0 additions & 25 deletions app/src/main/scala/app/logorrr/views/main/LogoRRRScene.scala

This file was deleted.

8 changes: 7 additions & 1 deletion app/src/main/scala/app/logorrr/views/main/LogoRRRStage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,17 @@ case class LogoRRRStage(stage: Stage) extends CanLog {
stage.setOnCloseRequest((_: WindowEvent) => closeApp())

private def closeApp(): Unit = {
// to save global filter state
for (t <- logorrrMain.ambp.logViewTabPane.getLogFileTabs) {
for ((f, i) <- t.filtersToolBar.activeFilters().zipWithIndex) {
LogoRRRGlobals.getLogFileSettings(t.pathAsString).setFilter(i, f)
}
}
LogoRRRGlobals.persist()
logorrrMain.shutdown()
LogoRRRGlobals.unbindWindow()
stage.sceneProperty.removeListener(LogoRRRStage.sceneListener)
logInfo(s"Stopped " + AppMeta.fullAppNameWithVersion)
logInfo(s"Stopped ${AppMeta.fullAppNameWithVersion}")
}

def show(): Unit = {
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/scala/app/logorrr/views/search/AnyFilter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package app.logorrr.views.search

import javafx.scene.paint.Color

class AnyFilter(filters: Set[Filter]) extends Fltr {
class AnyFilter(filters: Set[Filter]) extends Fltr(Color.WHITE) {

override val color: Color = {
if (filters.isEmpty) {
Expand All @@ -15,5 +15,5 @@ class AnyFilter(filters: Set[Filter]) extends Fltr {
}


override def applyMatch(searchTerm: String): Boolean = filters.exists(_.applyMatch(searchTerm))
override def matches(searchTerm: String): Boolean = filters.exists(_.matches(searchTerm))
}
30 changes: 26 additions & 4 deletions app/src/main/scala/app/logorrr/views/search/Filter.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package app.logorrr.views.search

import app.logorrr.views.search
import javafx.beans.property.SimpleBooleanProperty
import javafx.scene.paint.Color
import pureconfig.generic.semiauto.{deriveReader, deriveWriter}
import pureconfig.{ConfigReader, ConfigWriter}
Expand All @@ -24,7 +26,7 @@ object Filter {
* - a melange of all colors from all hits in all other cases
* */
def calcColor(value: String, filters: Seq[Fltr]): Color = {
val hits = filters.filter(sf => sf.applyMatch(value))
val hits = filters.filter(sf => sf.matches(value))
val color = {
if (hits.isEmpty) {
Color.LIGHTGREY
Expand All @@ -46,9 +48,29 @@ object Filter {
* The idea is to encode each search term with a color such that one can immediately spot an occurence in the views.
*
* @param pattern text to search for
* @param color associated color
* @param color associated color
* @param active is filter active
*/
class Filter(val pattern: String, val color: Color) extends Fltr {
class Filter(val pattern: String
, override val color: Color
, val active: Boolean) extends Fltr(color) {

override def applyMatch(searchTerm: String): Boolean = searchTerm.contains(pattern)

private lazy val activeProperty = new SimpleBooleanProperty(active)

def isActive = activeProperty.get()

def bind(filterButton: FilterButton): Unit = {
filterButton.selectedProperty().bindBidirectional(activeProperty)
}

def unbind(button: FilterButton): Unit = {
button.selectedProperty().unbindBidirectional(activeProperty)
}

override def matches(searchTerm: String): Boolean = searchTerm.contains(pattern)

def withActive(): Filter = {
new search.Filter(pattern, color, isActive)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import app.logorrr.views.ops.RectButton
import javafx.beans.{InvalidationListener, Observable}
import javafx.scene.control.{ContentDisplay, ToggleButton, Tooltip}

object SearchTag {
object FilterButton {
class RemoveFilterbutton(filter: Filter, removeFilter: Filter => Unit) extends RectButton(10, 10, filter.color, "remove") {
setOnAction(_ => removeFilter(filter))
setStyle(
Expand All @@ -18,21 +18,25 @@ object SearchTag {
/**
* Displays a search term and triggers displaying the results.
*/
class SearchTag(filter: Filter
, i: Int
, updateActiveFilter: () => Unit
, removeFilter: Filter => Unit) extends ToggleButton(filter.pattern) {
class FilterButton(val filter: Filter
, i: Int
, updateActiveFilter: () => Unit
, removeFilter: Filter => Unit) extends ToggleButton(filter.pattern) {

setTooltip(new Tooltip(if (i == 1) "one item found" else s"$i items found"))

if (!filter.isInstanceOf[UnclassifiedFilter]) {
if (!isUnclassified) {
setContentDisplay(ContentDisplay.RIGHT)
setGraphic(new SearchTag.RemoveFilterbutton(filter, removeFilter))
setGraphic(new FilterButton.RemoveFilterbutton(filter, removeFilter))
}
setSelected(true)
setSelected(filter.active)

selectedProperty().addListener(new InvalidationListener {
// if any of the buttons changes its selected value, reevaluate predicate
// and thus change contents of all views which display filtered List
override def invalidated(observable: Observable): Unit = updateActiveFilter()
})

def isUnclassified: Boolean = filter.isInstanceOf[UnclassifiedFilter]

}
84 changes: 47 additions & 37 deletions app/src/main/scala/app/logorrr/views/search/FiltersToolBar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import scala.jdk.CollectionConverters._

/** A toolbar with buttons which filter log events */
object FiltersToolBar {
/*
private val BackgroundSelectedStyle: String =
"""
|-fx-background-color: GREEN;
|-fx-border-width: 1px 1px 1px 1px;
|-fx-border-color: RED;
|""".stripMargin
*/
/*
private val BackgroundSelectedStyle: String =
"""
|-fx-background-color: GREEN;
|-fx-border-width: 1px 1px 1px 1px;
|-fx-border-color: RED;
|""".stripMargin
*/
}


Expand All @@ -30,8 +30,13 @@ object FiltersToolBar {
class FiltersToolBar(filteredList: FilteredList[LogEntry]
, removeFilter: Filter => Unit) extends ToolBar {

// setStyle(FiltersToolBar.BackgroundSelectedStyle)
var filterButtons: Map[Filter, FilterButton] = Map[Filter, FilterButton]()

var someUnclassifiedFilter: Option[(Filter, FilterButton)] = None

var occurrences: Map[Filter, Int] = Map().withDefaultValue(0)

/** will be bound to the current active filter list */
val filtersProperty = new SimpleListProperty[Filter]()

init()
Expand All @@ -46,35 +51,55 @@ class FiltersToolBar(filteredList: FilteredList[LogEntry]
private def processFiltersChange(change: ListChangeListener.Change[_ <: Filter]): Unit = {
while (change.next()) {
if (change.wasAdded()) {
change.getAddedSubList.asScala.foreach(addSearchTag)
change.getAddedSubList.asScala.foreach(addFilterButton)
updateUnclassified()
} else if (change.wasRemoved()) {
change.getRemoved.asScala.foreach(removeSearchTag)
change.getRemoved.asScala.foreach(removeFilterButton)
updateUnclassified()
}
}
}

var filterButtons: Map[Filter, SearchTag] = Map[Filter, SearchTag]()

var someUnclassifiedFilter: Option[(Filter, SearchTag)] = None

var occurrences: Map[Filter, Int] = Map().withDefaultValue(0)

private def updateOccurrences(sf: Filter): Unit = {
occurrences = occurrences + (sf -> filteredList.getSource.asScala.count(e => sf.applyMatch(e.value)))
occurrences = occurrences + (sf -> filteredList.getSource.asScala.count(e => sf.matches(e.value)))
}

private def updateUnclassified(): Unit = {
val unclassified = new UnclassifiedFilter(filterButtons.keySet)
updateOccurrences(unclassified)
val searchTag = new SearchTag(unclassified, occurrences(unclassified), updateActiveFilter, removeFilter)
val filterButton = new FilterButton(unclassified, occurrences(unclassified), updateActiveFilter, removeFilter)
someUnclassifiedFilter.foreach(ftb => getItems.remove(ftb._2))
getItems.add(0, searchTag)
someUnclassifiedFilter = Option((unclassified, searchTag))
getItems.add(0, filterButton)
someUnclassifiedFilter = Option((unclassified, filterButton))
updateActiveFilter()
}

private def addFilterButton(filter: Filter): Unit = {
updateOccurrences(filter)
val searchTag = new FilterButton(filter, occurrences(filter), updateActiveFilter, removeFilter)
filter.bind(searchTag)
getItems.add(searchTag)
filterButtons = filterButtons.updated(filter, searchTag)
}

private def removeFilterButton(filter: Filter): Unit = {
val button = filterButtons(filter)
filter.unbind(button)
getItems.remove(button)
filterButtons = filterButtons.removed(filter)
}

def activeFilters(): Seq[Filter] = {
(for (i <- getItems.asScala) yield {
val st = i.asInstanceOf[FilterButton]
if (st.isUnclassified) {
None
} else {
Option(st.filter.withActive())
}
}).flatten.toSeq
}

/**
* Filters are only active if selected.
*
Expand All @@ -87,25 +112,10 @@ class FiltersToolBar(filteredList: FilteredList[LogEntry]
filterButtons.filter(fst => fst._2.isSelected).keySet)
}

private def addSearchTag(filter: Filter): Unit = {
updateOccurrences(filter)
val searchTag = new SearchTag(filter, occurrences(filter), updateActiveFilter, removeFilter)
getItems.add(searchTag)
filterButtons = filterButtons + (filter -> searchTag)
}

private def removeSearchTag(filter: Filter): Unit = {
getItems.remove(filterButtons(filter))
filterButtons = filterButtons - filter
}

def updateActiveFilter(): Unit = {
val filter = computeCurrentFilter()
filteredList.setPredicate((entry: LogEntry) => filter.applyMatch(entry.value))
filteredList.setPredicate((entry: LogEntry) => computeCurrentFilter().matches(entry.value))
}

updateUnclassified()


}

Expand Down
Loading

0 comments on commit 23e91ce

Please sign in to comment.