Skip to content

Commit

Permalink
Add filters panel and update
Browse files Browse the repository at this point in the history
  • Loading branch information
waleedyaseen committed Aug 1, 2024
1 parent 57ccde4 commit 8eee6cf
Show file tree
Hide file tree
Showing 19 changed files with 376 additions and 55 deletions.
15 changes: 0 additions & 15 deletions .run/Proxy.run.xml

This file was deleted.

10 changes: 7 additions & 3 deletions gui/proxy-tool/src/main/kotlin/net/rsprox/gui/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ public class App {
private val frame = JFrame()
private val sessionsPanel = SessionsPanel(this)
public val statusBar: StatusBar = StatusBar()
public val service: ProxyService = ProxyService(UnpooledByteBufAllocator.DEFAULT)
public lateinit var service: ProxyService

public fun init() {
// We have to start before populating right now.
service = ProxyService(UnpooledByteBufAllocator.DEFAULT)
service.start()


val defaultSize = UIScale.scale(Dimension(800, 600))

// Configure the app frame.
Expand All @@ -45,7 +50,6 @@ public class App {
public fun start() {
frame.isVisible = true
frame.setLocationRelativeTo(null)
service.start()
}

private fun setupMenuBar() {
Expand All @@ -66,7 +70,7 @@ public class App {

private fun createSideBar() = SideBar().apply {
addButton(AppIcons.Settings, "Sessions", JPanel())
addButton(AppIcons.Filter, "Filters", JPanel())
addButton(AppIcons.Filter, "Filters", FiltersSidePanel(service))
selectedIndex = -1
}

Expand Down
9 changes: 9 additions & 0 deletions gui/proxy-tool/src/main/kotlin/net/rsprox/gui/AppIcons.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package net.rsprox.gui

import com.formdev.flatlaf.extras.FlatSVGIcon
import com.formdev.flatlaf.extras.FlatSVGUtils
import javax.imageio.ImageIO
import javax.swing.ImageIcon

public object AppIcons {

public val Add: FlatSVGIcon = loadSvgIcon("add")
public val Collapse: FlatSVGIcon = loadSvgIcon("collapse")
public val Copy: FlatSVGIcon = loadSvgIcon("copy")
public val Delete: FlatSVGIcon = loadSvgIcon("delete")
public val Expand: FlatSVGIcon = loadSvgIcon("expand")
public val Filter: FlatSVGIcon = loadSvgIcon("filter")
public val Java: ImageIcon = loadIcon("java")
public val Native: ImageIcon = loadIcon("native")
Expand All @@ -17,6 +22,10 @@ public object AppIcons {
public val User: FlatSVGIcon = loadSvgIcon("user")
public val Web: FlatSVGIcon = loadSvgIcon("web")

// favicons
public val Favicon16: ImageIcon = ImageIcon(FlatSVGUtils.svg2image("/favicon.svg", 16, 16))


private fun loadIcon(name: String): ImageIcon {
val image = ImageIO.read(javaClass.getResource("/icons/$name.png"))
return ImageIcon(image)
Expand Down
247 changes: 247 additions & 0 deletions gui/proxy-tool/src/main/kotlin/net/rsprox/gui/FiltersSidePanel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
package net.rsprox.gui

import com.formdev.flatlaf.extras.components.FlatButton
import com.formdev.flatlaf.extras.components.FlatButton.ButtonType
import com.formdev.flatlaf.extras.components.FlatLabel
import com.formdev.flatlaf.extras.components.FlatSeparator
import com.formdev.flatlaf.extras.components.FlatTabbedPane
import com.formdev.flatlaf.icons.FlatCheckBoxIcon
import net.miginfocom.swing.MigLayout
import net.rsprox.gui.dialogs.Dialogs
import net.rsprox.proxy.ProxyService
import net.rsprox.shared.StreamDirection
import net.rsprox.shared.filters.PropertyFilter
import net.rsprox.shared.filters.ProtCategory
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.event.ActionListener
import java.awt.event.ItemEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.*

public class FiltersSidePanel(
private val proxyService: ProxyService
) : JPanel() {

private val presetsBoxModel = DefaultComboBoxModel<String>()
private val presetsBox = JComboBox(presetsBoxModel)
private val copyButton = createControlButton(AppIcons.Copy, "Copy selected preset into new preset")
private val createButton = createControlButton(AppIcons.Add, "Create new preset from default filters")
private val deleteButton = createControlButton(AppIcons.Delete, "Delete selected preset")
private val checkboxes = hashMapOf<PropertyFilter, JCheckBox>()

init {
layout = MigLayout("fill, ins panel, wrap 1, hidemode 3", "[grow]", "[][][][grow, fill]")
minimumSize = Dimension(210, 0)

presetsBox.addItemListener { e ->
if (e.stateChange != ItemEvent.SELECTED) return@addItemListener
proxyService.filterSetStore.setActive(presetsBox.selectedIndex)
updateButtonState()
updateFilterState()
}

createButton.addActionListener {
val name = Dialogs.showInputString(parent = this, "Create new preset", "Enter preset name")
?: return@addActionListener
if (presetsBoxModel.getIndexOf(name) != -1) {
Dialogs.showError(parent = this, message = "Preset with name '$name' already exists.")
return@addActionListener
}
proxyService.filterSetStore.create(name)
populatePresets()
presetsBoxModel.selectedItem = name
}

deleteButton.addActionListener {
val selectedIndex = presetsBox.selectedIndex
if (selectedIndex == -1) return@addActionListener
val presetName = presetsBox.selectedItem as String
val result = JOptionPane.showConfirmDialog(
this,
"Are you sure you want to delete the selected preset?",
"Delete '${presetName}' preset",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE
)
if (result != JOptionPane.YES_OPTION) return@addActionListener
proxyService.filterSetStore.delete(selectedIndex)
populatePresets()
}

copyButton.addActionListener {
val selectedIndex = presetsBox.selectedIndex
if (selectedIndex == -1) return@addActionListener
val name = Dialogs.showInputString(parent = this, "Copy preset", "Enter new preset name")
?: return@addActionListener
if (presetsBoxModel.getIndexOf(name) != -1) {
Dialogs.showError(parent = this, message = "Preset with name '$name' already exists.")
return@addActionListener
}
val filterSet = proxyService.filterSetStore.get(selectedIndex) ?: return@addActionListener
val newFilterSet = proxyService.filterSetStore.create(name)
for (property in PropertyFilter.entries) {
newFilterSet[property] = filterSet[property]
}
populatePresets()
presetsBoxModel.selectedItem = name
}

add(FlatLabel().apply { text = "Presets:" })

add(presetsBox, "growx")

val controlPanel = JPanel().apply {
layout = MigLayout("insets 0", "[grow][grow][grow]", "[32px]")
add(copyButton, "grow, hmin 32px")
add(createButton, "grow, hmin 32px")
add(deleteButton, "grow, hmin 32px")
}

add(controlPanel, "growx")

val tabbedGroup = FlatTabbedPane()
tabbedGroup.addTab("Server to Client", createFilterPanel(StreamDirection.SERVER_TO_CLIENT))
tabbedGroup.addTab("Client to Server", createFilterPanel(StreamDirection.CLIENT_TO_SERVER))

add(tabbedGroup, "grow, pushy")

populatePresets()
updateButtonState()
updateFilterState()
}

private fun updateFilterState() {
val active = proxyService.filterSetStore.getActive()
for ((property, checkbox) in checkboxes) {
checkbox.isSelected = active[property]
}
}

private fun updateButtonState() {
val selectedIndex = presetsBox.selectedIndex
if (selectedIndex == -1) return
deleteButton.isEnabled = selectedIndex != 0
checkboxes.values.forEach { it.isEnabled = selectedIndex != 0 }
}

private fun createFilterPanel(serverToClient: StreamDirection): JScrollPane {
val scrollPane = JScrollPane(FiltersPanel(serverToClient)).apply {
horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED
border = null
}
return scrollPane
}

private fun populatePresets() {
val oldSelectedItem = presetsBox.selectedItem
presetsBoxModel.removeAllElements()
for (i in 0 until proxyService.filterSetStore.size) {
val filterSet = proxyService.filterSetStore.get(i) ?: continue
presetsBoxModel.addElement(filterSet.getName())
}
if (oldSelectedItem != null) {
val selectedIndex = presetsBoxModel.getIndexOf(oldSelectedItem)
if (selectedIndex == -1) {
presetsBox.selectedIndex = presetsBoxModel.size - 1
} else {
presetsBox.selectedIndex = selectedIndex
}
} else {
presetsBox.selectedIndex = presetsBoxModel.size - 1
}
}

private fun createControlButton(icon: Icon, tooltip: String) = FlatButton().apply {
this.buttonType = ButtonType.square
this.icon = icon
this.toolTipText = tooltip
isFocusPainted = false
}

private inner class FiltersPanel(private val direction: StreamDirection) : JPanel() {
init {
layout = BoxLayout(this, BoxLayout.Y_AXIS)

val filteredProperties = PropertyFilter.entries
.filter { it.direction == direction }
.groupBy { it.category }

for ((category, properties) in filteredProperties) {
add(createCategoryPanel(category, properties))
}
}
}

private fun createCategoryPanel(category: ProtCategory, properties: List<PropertyFilter>) = JPanel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS)

val content = JPanel()
content.border = null
content.layout = BoxLayout(content, BoxLayout.Y_AXIS)
for (property in properties) {
content.add(createPropertyFilterPanel(property))
}

add(createCategoryHeaderPanel(content, category))
add(FlatSeparator())

add(content)

}

private fun createCategoryHeaderPanel(content: JPanel, category: ProtCategory) = JPanel(BorderLayout()).apply {
val toggle = FlatButton()
toggle.toolTipText = "Collapse"
toggle.icon = AppIcons.Collapse
toggle.buttonType = ButtonType.toolBarButton

val collapseAction = ActionListener {
content.isVisible = !content.isVisible
toggle.icon = if (content.isVisible) AppIcons.Collapse else AppIcons.Expand
toggle.toolTipText = if (content.isVisible) "Collapse" else "Expand"
}

toggle.addActionListener(collapseAction)
add(toggle, BorderLayout.WEST)

val label = FlatLabel()
label.text = category.label
label.labelType = FlatLabel.LabelType.large
label.toolTipText = category.label
add(label, BorderLayout.CENTER)

label.addMouseListener(object : MouseAdapter() {
override fun mouseReleased(e: MouseEvent) {
if (SwingUtilities.isLeftMouseButton(e)
&& e.x >= 0 && e.x <= label.width && e.y >= 0 && e.y <= label.height
) {
collapseAction.actionPerformed(null)
}
}
})
}

private fun createPropertyFilterPanel(property: PropertyFilter) = JPanel().apply {
layout = BorderLayout()
border = BorderFactory.createEmptyBorder(5, 5, 5, 5)

val checkbox = JCheckBox()
checkbox.addActionListener {
val active = proxyService.filterSetStore.getActive()
active[property] = checkbox.isSelected
}
add(checkbox, BorderLayout.EAST)

val label = FlatLabel()
label.labelType = FlatLabel.LabelType.large
label.text = property.label
label.toolTipText = property.tooltip
add(label, BorderLayout.CENTER)

checkboxes[property] = checkbox
}
}

28 changes: 28 additions & 0 deletions gui/proxy-tool/src/main/kotlin/net/rsprox/gui/dialogs/Dialogs.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package net.rsprox.gui.dialogs

import java.awt.Component
import javax.swing.JOptionPane

public object Dialogs {

public fun showInputString(
parent: Component? = null,
title: String,
message: String,
initialValue: String = ""
): String? {
return JOptionPane.showInputDialog(
parent,
message,
title,
JOptionPane.QUESTION_MESSAGE,
null,
null,
initialValue
) as String?
}

public fun showError(parent: Component? = null, title: String = "Error", message: String) {
JOptionPane.showMessageDialog(parent, message, title, JOptionPane.ERROR_MESSAGE)
}
}
4 changes: 4 additions & 0 deletions gui/proxy-tool/src/main/kotlin/net/rsprox/gui/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import com.formdev.flatlaf.extras.FlatInspector
import com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialDeepOceanIJTheme
import net.rsprox.gui.App
import javax.swing.SwingUtilities
import javax.swing.UIManager

public fun main() {
FlatInspector.install("ctrl shift alt X")
SwingUtilities.invokeLater {
UIManager.put("Component.focusWidth", 0)

FlatMaterialDeepOceanIJTheme.setup()

val app = App()
app.init()
app.start()
Expand Down
Loading

0 comments on commit 8eee6cf

Please sign in to comment.