@@ -2,10 +2,16 @@ package com.styx.ui
22
33import com.styx.api.SteamApiHelper
44import com.styx.models.Game
5+ import com.styx.models.GameType
6+ import com.styx.utils.formatTimePlayed
7+ import java.awt.BorderLayout
58import java.awt.Color
9+ import java.awt.Desktop
610import java.awt.Dimension
711import java.awt.FlowLayout
812import java.awt.Font
13+ import java.io.File
14+ import java.net.URI
915import javax.swing.BorderFactory
1016import javax.swing.Box
1117import javax.swing.BoxLayout
@@ -22,12 +28,13 @@ import javax.swing.border.EmptyBorder
2228
2329class GameConfigDialog (
2430 private val game : Game ,
25- parent : JFrame ,
31+ private val launcher : GameLauncher ,
2632 private val onLaunchOptions : (Game ) -> Unit ,
2733 private val onPrefixManager : (Game ) -> Unit ,
2834 private val onProtonManager : (Game ) -> Unit ,
29- private val onChangePrefix : (Game ) -> Unit
30- ) : JDialog(parent, " Configure - ${game.name} " , true ) {
35+ private val onChangePrefix : (Game ) -> Unit ,
36+ private val onRename : (Game ) -> Unit
37+ ) : JDialog(launcher, " Configure - ${game.name} " , true ) {
3138
3239 private val verboseCheckbox = JCheckBox (" Enable Verbose Logging (show all Wine debug)" , game.verboseLogging)
3340 private val steamAppIdInput = JTextField (20 )
@@ -145,6 +152,72 @@ class GameConfigDialog(
145152
146153 actionsPanel.add(changePrefixBtn)
147154 mainPanel.add(actionsPanel)
155+ mainPanel.add(Box .createVerticalStrut(15 ))
156+
157+ val additionalActionsPanel = JPanel ()
158+ additionalActionsPanel.layout = BoxLayout (additionalActionsPanel, BoxLayout .Y_AXIS )
159+ additionalActionsPanel.border = BorderFactory .createTitledBorder(" Quick Actions" )
160+ additionalActionsPanel.alignmentX = LEFT_ALIGNMENT
161+
162+ val statsBtn = JButton (" Show Statistics" ).apply {
163+ maximumSize = Dimension (Short .MAX_VALUE .toInt(), 32 )
164+ alignmentX = LEFT_ALIGNMENT
165+ toolTipText = " View game statistics"
166+ addActionListener {
167+ showStatistics()
168+ }
169+ }
170+ additionalActionsPanel.add(statsBtn)
171+ additionalActionsPanel.add(Box .createVerticalStrut(8 ))
172+
173+ val renameBtn = JButton (" Rename Game" ).apply {
174+ maximumSize = Dimension (Short .MAX_VALUE .toInt(), 32 )
175+ alignmentX = LEFT_ALIGNMENT
176+ toolTipText = " Rename this game"
177+ addActionListener {
178+ renameGame()
179+ }
180+ }
181+ additionalActionsPanel.add(renameBtn)
182+ additionalActionsPanel.add(Box .createVerticalStrut(8 ))
183+
184+ val openGameLocationBtn = JButton (" Open Game Location" ).apply {
185+ maximumSize = Dimension (Short .MAX_VALUE .toInt(), 32 )
186+ alignmentX = LEFT_ALIGNMENT
187+ toolTipText = " Open the game's directory in file manager"
188+ addActionListener {
189+ openGameLocation()
190+ }
191+ }
192+ additionalActionsPanel.add(openGameLocationBtn)
193+
194+ if (game.getGameType() == GameType .WINDOWS ) {
195+ additionalActionsPanel.add(Box .createVerticalStrut(8 ))
196+ val openPrefixBtn = JButton (" Open Wine Prefix Location" ).apply {
197+ maximumSize = Dimension (Short .MAX_VALUE .toInt(), 32 )
198+ alignmentX = LEFT_ALIGNMENT
199+ toolTipText = " Open the wine prefix directory"
200+ addActionListener {
201+ openPrefixLocation()
202+ }
203+ }
204+ additionalActionsPanel.add(openPrefixBtn)
205+
206+ if (! game.steamAppId.isNullOrEmpty()) {
207+ additionalActionsPanel.add(Box .createVerticalStrut(8 ))
208+ val protonDbBtn = JButton (" Open ProtonDB Page" ).apply {
209+ maximumSize = Dimension (Short .MAX_VALUE .toInt(), 32 )
210+ alignmentX = LEFT_ALIGNMENT
211+ toolTipText = " Open this game's ProtonDB page"
212+ addActionListener {
213+ openProtonDB()
214+ }
215+ }
216+ additionalActionsPanel.add(protonDbBtn)
217+ }
218+ }
219+
220+ mainPanel.add(additionalActionsPanel)
148221 mainPanel.add(Box .createVerticalGlue())
149222
150223 val buttonPanel = JPanel (FlowLayout (FlowLayout .RIGHT , 8 , 0 ))
@@ -223,4 +296,144 @@ class GameConfigDialog(
223296
224297 progressDialog.isVisible = true
225298 }
299+
300+ private fun showStatistics () {
301+ val statsWindow = JDialog (this , " Stats - ${game.name} " , true )
302+ statsWindow.defaultCloseOperation = JDialog .DISPOSE_ON_CLOSE
303+ statsWindow.setSize(400 , 400 )
304+ statsWindow.minimumSize = Dimension (400 , 400 )
305+ statsWindow.setLocationRelativeTo(this )
306+
307+ val mainPanel = JPanel (BorderLayout ())
308+ mainPanel.border = EmptyBorder (20 , 20 , 20 , 20 )
309+ mainPanel.background = Color (34 , 35 , 36 )
310+
311+ val titleLabel = JLabel (" Statistics for ${game.name} " )
312+ titleLabel.font = titleLabel.font.deriveFont(Font .BOLD , 16f )
313+ titleLabel.border = EmptyBorder (0 , 0 , 20 , 0 )
314+ titleLabel.foreground = launcher.globalSettings.theme.getGameTitleColorObject()
315+ mainPanel.add(titleLabel, BorderLayout .NORTH )
316+
317+ val tableData = arrayOf(
318+ arrayOf(" Time Played" , formatTimePlayed(game.timePlayed)),
319+ arrayOf(" Times Opened" , game.timesOpened.toString()),
320+ arrayOf(" Times Crashed" , game.timesCrashed.toString()),
321+ arrayOf(" Wineprefix Path" , game.prefix),
322+ arrayOf(" Compatibility Layer" , game.protonVersion ? : " Wine (default)" )
323+ )
324+
325+ val columnNames = arrayOf(" Statistic" , " Value" )
326+ val tableModel = javax.swing.table.DefaultTableModel (tableData, columnNames)
327+ val table = javax.swing.JTable (tableModel)
328+ table.font = table.font.deriveFont(14f )
329+ table.rowHeight = 30
330+ table.setEnabled(false )
331+
332+ val scrollPane = javax.swing.JScrollPane (table)
333+ mainPanel.add(scrollPane, BorderLayout .CENTER )
334+
335+ statsWindow.contentPane = mainPanel
336+ statsWindow.isVisible = true
337+ }
338+
339+ private fun renameGame () {
340+ val newName = JOptionPane .showInputDialog(
341+ this ,
342+ " Enter new name for '${game.name} ':" ,
343+ " Rename Game" ,
344+ JOptionPane .QUESTION_MESSAGE ,
345+ null ,
346+ null ,
347+ game.name
348+ ) as ? String
349+
350+ if (newName != null && newName.isNotBlank() && newName != game.name) {
351+ game.name = newName
352+ onRename(game)
353+ title = " Configure - ${game.name} "
354+ }
355+ }
356+
357+ private fun openGameLocation () {
358+ if (game.getGameType() == GameType .STEAM ) {
359+ JOptionPane .showMessageDialog(
360+ this ,
361+ " Steam App ID: ${game.executable} \n This game is launched via Steam." ,
362+ " Steam Game Info" ,
363+ JOptionPane .INFORMATION_MESSAGE
364+ )
365+ return
366+ }
367+
368+ val gameFile = File (game.executable)
369+ val gameDirectory = gameFile.parentFile
370+
371+ if (gameDirectory != null && gameDirectory.exists()) {
372+ try {
373+ if (Desktop .isDesktopSupported()) {
374+ Desktop .getDesktop().open(gameDirectory)
375+ } else {
376+ ProcessBuilder (" xdg-open" , gameDirectory.absolutePath).start()
377+ }
378+ } catch (e: Exception ) {
379+ JOptionPane .showMessageDialog(
380+ this ,
381+ " Failed to open game location: ${e.message} " ,
382+ " Error" ,
383+ JOptionPane .ERROR_MESSAGE
384+ )
385+ }
386+ } else {
387+ JOptionPane .showMessageDialog(
388+ this ,
389+ " Game location not found: ${gameDirectory?.absolutePath ? : game.executable} " ,
390+ " Error" ,
391+ JOptionPane .ERROR_MESSAGE
392+ )
393+ }
394+ }
395+
396+ private fun openPrefixLocation () {
397+ val prefixDirectory = File (game.prefix)
398+
399+ if (prefixDirectory.exists()) {
400+ try {
401+ if (Desktop .isDesktopSupported()) {
402+ Desktop .getDesktop().open(prefixDirectory)
403+ } else {
404+ ProcessBuilder (" xdg-open" , prefixDirectory.absolutePath).start()
405+ }
406+ } catch (e: Exception ) {
407+ JOptionPane .showMessageDialog(
408+ this ,
409+ " Failed to open wine prefix location: ${e.message} " ,
410+ " Error" ,
411+ JOptionPane .ERROR_MESSAGE
412+ )
413+ }
414+ } else {
415+ JOptionPane .showMessageDialog(
416+ this ,
417+ " Wine prefix not found: ${game.prefix} " ,
418+ " Error" ,
419+ JOptionPane .ERROR_MESSAGE
420+ )
421+ }
422+ }
423+
424+ private fun openProtonDB () {
425+ if (! game.steamAppId.isNullOrEmpty()) {
426+ try {
427+ val uri = URI (" https://www.protondb.com/app/${game.steamAppId} " )
428+ Desktop .getDesktop().browse(uri)
429+ } catch (e: Exception ) {
430+ JOptionPane .showMessageDialog(
431+ this ,
432+ " Failed to open ProtonDB: ${e.message} " ,
433+ " Error" ,
434+ JOptionPane .ERROR_MESSAGE
435+ )
436+ }
437+ }
438+ }
226439}
0 commit comments