Add Save/Save As buttons with dirty state tracking to EQ dialog

This commit is contained in:
Gianluca Boiano
2026-02-08 12:46:20 +01:00
parent f302612d53
commit a2e4ab2380
17 changed files with 397 additions and 50 deletions
+152 -24
View File
@@ -2,6 +2,7 @@ package dialogs
import (
"fmt"
"math"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
@@ -42,6 +43,9 @@ type GraphicEqualizer struct {
parentWindow fyne.Window
isApplyingPreset bool // Flag to prevent clearing profile during preset application
currentEQType string // Current EQ type ("ISO10Band" or "ISO15Band")
isDirty bool // true when sliders modified since last preset load/save
loadedPreset *backend.EQPreset // currently loaded preset (nil if none)
saveBtn *widget.Button // reference for enable/disable control
}
func NewGraphicEqualizer(preamp float64, bandFreqs []string, bandGains []float64, eqType string, presetMgr *backend.EQPresetManager, parentWindow fyne.Window, activePresetName string) *GraphicEqualizer {
@@ -57,7 +61,16 @@ func NewGraphicEqualizer(preamp float64, bandFreqs []string, bandGains []float64
// Set the dropdown to the active preset if one exists
if activePresetName != "" {
g.setActivePreset(activePresetName)
// Populate loadedPreset and detect dirty state on dialog reopen
for i, p := range g.eqPresets {
if p.Name == activePresetName {
g.loadedPreset = &g.eqPresets[i]
g.isDirty = !g.matchesPreset(p)
break
}
}
}
g.updateSaveButtonState()
return g
}
@@ -115,9 +128,15 @@ func (g *GraphicEqualizer) buildSliders(preamp float64, bands []string, bandGain
}
})
// Save button
saveBtn := widget.NewButton(lang.L("Save"), func() {
g.showSavePresetDialog()
// Save button (overwrites current loaded preset)
g.saveBtn = widget.NewButton(lang.L("Save"), func() {
g.saveCurrentPreset()
})
g.saveBtn.Disable() // starts disabled
// Save As button (always enabled, opens name-entry dialog)
saveAsBtn := widget.NewButton(lang.L("Save As"), func() {
g.showSaveAsDialog()
})
// Delete button
@@ -149,7 +168,8 @@ func (g *GraphicEqualizer) buildSliders(preamp float64, bands []string, bandGain
g.presetSelect,
g.autoEQBtn,
layout.NewSpacer(),
saveBtn,
g.saveBtn,
saveAsBtn,
deleteBtn,
resetBtn,
),
@@ -187,8 +207,12 @@ func (g *GraphicEqualizer) buildSliderArea(preamp float64, bands []string, bandG
g.OnPreampChanged(f)
}
g.preampSlider.UpdateToolTip()
if !g.isApplyingPreset && g.OnManualAdjustment != nil {
g.OnManualAdjustment()
if !g.isApplyingPreset {
g.isDirty = true
g.updateSaveButtonState()
if g.OnManualAdjustment != nil {
g.OnManualAdjustment()
}
}
}
g.preampSlider.UpdateToolTip()
@@ -208,8 +232,12 @@ func (g *GraphicEqualizer) buildSliderArea(preamp float64, bands []string, bandG
g.OnChanged(_i, f)
}
g.bandSliders[_i].UpdateToolTip()
if !g.isApplyingPreset && g.OnManualAdjustment != nil {
g.OnManualAdjustment()
if !g.isApplyingPreset {
g.isDirty = true
g.updateSaveButtonState()
if g.OnManualAdjustment != nil {
g.OnManualAdjustment()
}
}
}
l := newCaptionTextSizeLabel(band, fyne.TextAlignCenter)
@@ -255,6 +283,11 @@ func (g *GraphicEqualizer) RebuildForEQType(eqType string, bandGains []float64)
g.sliderArea = newSliderArea
g.container.Objects = []fyne.CanvasObject{g.topBar, g.sliderArea}
g.container.Refresh()
// Clear loaded preset when EQ type changes
g.loadedPreset = nil
g.isDirty = false
g.updateSaveButtonState()
}
func (g *GraphicEqualizer) updatePresetSelect() {
@@ -346,6 +379,12 @@ func (g *GraphicEqualizer) applyPreset(preset backend.EQPreset) {
}
}
}
// Track the loaded preset and clear dirty state
presetCopy := preset
g.loadedPreset = &presetCopy
g.isDirty = false
g.updateSaveButtonState()
}
func (g *GraphicEqualizer) getCurrentSettings() backend.EQPreset {
@@ -360,12 +399,61 @@ func (g *GraphicEqualizer) getCurrentSettings() backend.EQPreset {
}
}
func (g *GraphicEqualizer) showSavePresetDialog() {
func (g *GraphicEqualizer) updateSaveButtonState() {
if g.saveBtn == nil {
return
}
if g.loadedPreset != nil && !g.loadedPreset.IsBuiltin && g.isDirty {
g.saveBtn.Enable()
} else {
g.saveBtn.Disable()
}
}
func (g *GraphicEqualizer) saveCurrentPreset() {
if g.loadedPreset == nil || g.loadedPreset.IsBuiltin {
return
}
g.savePresetWithName(g.loadedPreset.Name)
}
func (g *GraphicEqualizer) savePresetWithName(name string) {
preset := g.getCurrentSettings()
preset.Name = name
preset.IsBuiltin = false
if err := g.presetManager.SavePreset(preset); err != nil {
dialog.ShowError(err, g.parentWindow)
return
}
// Update loaded preset and clear dirty state
g.loadedPreset = &preset
g.isDirty = false
g.updateSaveButtonState()
// Reload presets and update UI
g.loadPresets()
g.updatePresetSelect()
// Select the newly saved preset
g.presetSelect.SetSelected(preset.Name + " *")
if g.OnPresetSelected != nil {
g.OnPresetSelected(preset.Name)
}
}
func (g *GraphicEqualizer) showSaveAsDialog() {
nameEntry := widget.NewEntry()
nameEntry.SetPlaceHolder(lang.L("Preset name"))
// Pre-fill with loaded preset name if it's a custom preset
if g.loadedPreset != nil && !g.loadedPreset.IsBuiltin {
nameEntry.SetText(g.loadedPreset.Name)
}
formDialog := dialog.NewForm(
lang.L("Save Preset"),
lang.L("Save Preset As"),
lang.L("Save"),
lang.L("Cancel"),
[]*widget.FormItem{
@@ -376,24 +464,38 @@ func (g *GraphicEqualizer) showSavePresetDialog() {
return
}
preset := g.getCurrentSettings()
preset.Name = nameEntry.Text
preset.IsBuiltin = false
name := nameEntry.Text
if err := g.presetManager.SavePreset(preset); err != nil {
dialog.ShowError(err, g.parentWindow)
return
// Check if name matches a builtin preset
for _, p := range g.eqPresets {
if p.Name == name && p.IsBuiltin {
dialog.ShowInformation(
lang.L("Invalid Name"),
lang.L("Cannot use the name of a builtin preset"),
g.parentWindow,
)
return
}
}
// Reload presets and update UI
g.loadPresets()
g.updatePresetSelect()
// Select the newly saved preset
g.presetSelect.SetSelected(preset.Name + " *")
if g.OnPresetSelected != nil {
g.OnPresetSelected(preset.Name)
// Check if name matches an existing custom preset
for _, p := range g.eqPresets {
if p.Name == name && !p.IsBuiltin {
dialog.ShowConfirm(
lang.L("Overwrite Preset"),
fmt.Sprintf(lang.L("Preset '%s' already exists. Overwrite?"), name),
func(overwrite bool) {
if overwrite {
g.savePresetWithName(name)
}
},
g.parentWindow,
)
return
}
}
g.savePresetWithName(name)
},
g.parentWindow,
)
@@ -402,6 +504,32 @@ func (g *GraphicEqualizer) showSavePresetDialog() {
formDialog.Show()
}
// matchesPreset compares current slider values against a preset
func (g *GraphicEqualizer) matchesPreset(preset backend.EQPreset) bool {
if g.preampSlider == nil {
return false
}
if math.Abs(g.preampSlider.Value-preset.Preamp) > 0.05 {
return false
}
if len(g.bandSliders) != len(preset.Bands) {
return false
}
for i, slider := range g.bandSliders {
if math.Abs(slider.Value-preset.Bands[i]) > 0.05 {
return false
}
}
return true
}
// ClearLoadedPresetState clears the loaded preset and dirty state
func (g *GraphicEqualizer) ClearLoadedPresetState() {
g.loadedPreset = nil
g.isDirty = false
g.updateSaveButtonState()
}
func (g *GraphicEqualizer) showDeletePresetDialog() {
selected := g.presetSelect.Selected
if selected == "" {
+4 -4
View File
@@ -501,12 +501,11 @@ func (s *SettingsDialog) createEqualizerTab(eqBands []string) *container.TabItem
debouncer()
}
geq.OnManualAdjustment = func() {
// Clear AutoEQ profile and preset when user manually adjusts sliders
// Clear AutoEQ profile when user manually adjusts sliders
// Preset name persists so Save button can overwrite the loaded preset
s.config.LocalPlayback.AutoEQProfilePath = ""
s.config.LocalPlayback.AutoEQProfileName = ""
s.config.LocalPlayback.ActiveEQPresetName = ""
geq.ClearProfileLabel()
geq.ClearPresetSelection()
}
geq.OnLoadAutoEQProfile = func() {
s.openAutoEQBrowser(geq, debouncer)
@@ -634,8 +633,9 @@ func (s *SettingsDialog) applyAutoEQProfile(profile *backend.AutoEQProfile, geq
}
geq.applyPreset(preset)
// Clear preset dropdown since AutoEQ is now active
// Clear preset dropdown and loaded preset state since AutoEQ is now active
geq.ClearPresetSelection()
geq.ClearLoadedPresetState()
// Show profile label
geq.SetProfileLabel(profile.Name)