Skip to content

Commit f8cd51f

Browse files
committed
Modular settings with tabbed UI, improved custom path window interaction
1 parent d824e4e commit f8cd51f

11 files changed

Lines changed: 334 additions & 24 deletions

CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,12 @@ target_sources(${PROJECT_NAME}
106106
src/HarpLogger.cpp
107107
src/errors.h
108108
src/utils.h
109-
109+
src/settings/SettingsBox.h
110+
src/settings/SettingsBox.cpp
111+
src/settings/GeneralSettingsTab.h
112+
src/settings/GeneralSettingsTab.cpp
113+
src/settings/AudioSettingsTab.h
114+
src/settings/AudioSettingsTab.cpp
110115
src/gradio/GradioClient.cpp
111116
src/external/magic_enum.hpp
112117

JUCE

Submodule JUCE updated from 9f64325 to fd2e21c

src/AppSettings.h

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,32 @@ class AppSettings
6060

6161
return defaultValue;
6262
}
63-
64-
/** Set a value of any type that can be converted to string */
63+
64+
// Set value specific for boolean type
65+
static void setValue(const juce::String& keyName, bool value, bool saveImmediately = false)
66+
{
67+
if (auto* settings = getUserSettings())
68+
{
69+
settings->setValue(keyName, value ? "true" : "false");
70+
71+
if (saveImmediately)
72+
settings->saveIfNeeded();
73+
}
74+
}
75+
76+
77+
/** Set a value of any type that can be converted to string */
6578
template <typename ValueType>
6679
static void setValue(const juce::String& keyName, const ValueType& value, bool saveImmediately = false)
67-
{
80+
{
6881
if (auto* settings = getUserSettings())
6982
{
7083
settings->setValue(keyName, juce::String(value));
71-
84+
7285
if (saveImmediately)
7386
settings->saveIfNeeded();
7487
}
75-
}
88+
}
7689

7790
/** Save settings to disk if needed */
7891
static void saveIfNeeded()

src/MainComponent.h

Lines changed: 122 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434

3535
#include "windows/AboutWindow.h"
3636
#include "AppSettings.h"
37+
#include "settings/SettingsBox.h"
38+
3739

3840
using namespace juce;
3941

@@ -110,18 +112,24 @@ class MainComponent : public Component,
110112
saveAs = 0x2003,
111113
undo = 0x2004,
112114
redo = 0x2005,
113-
login = 0x2006
114-
// settings = 0x2007,
115+
login = 0x2006,
116+
settings = 0x2007
115117
};
116118

117-
StringArray getMenuBarNames() override { return { "File" }; }
119+
StringArray getMenuBarNames() override
120+
{
121+
//DBG("getMenuBarNames() called");
122+
return { "File" };
123+
}
118124

119125
// In mac, we want the "about" command to be in the application menu ("HARP" tab)
120126
// For now, this is not used, as the extra commands appear grayed out
121127
std::unique_ptr<PopupMenu> getMacExtraMenu()
122128
{
123129
auto menu = std::make_unique<PopupMenu>();
124130
menu->addCommandItem(&commandManager, CommandIDs::about);
131+
menu->addCommandItem(&commandManager, CommandIDs::settings);
132+
menu->addCommandItem(&commandManager, CommandIDs::login);
125133
return menu;
126134
}
127135

@@ -137,10 +145,14 @@ class MainComponent : public Component,
137145
menu.addCommandItem(&commandManager, CommandIDs::undo);
138146
menu.addCommandItem(&commandManager, CommandIDs::redo);
139147
menu.addSeparator();
140-
// menu.addCommandItem (&commandManager, CommandIDs::settings);
141-
// menu.addSeparator();
142-
menu.addCommandItem(&commandManager, CommandIDs::login);
143-
menu.addCommandItem(&commandManager, CommandIDs::about);
148+
//menu.addCommandItem (&commandManager, CommandIDs::settings);
149+
menu.addSeparator();
150+
//menu.addCommandItem(&commandManager, CommandIDs::login);
151+
//menu.addCommandItem(&commandManager, CommandIDs::about);
152+
}
153+
else
154+
{
155+
DBG("Unknown menu name: " << menuName);
144156
}
145157
return menu;
146158
}
@@ -158,7 +170,7 @@ class MainComponent : public Component,
158170
const CommandID ids[] = {
159171
CommandIDs::open, CommandIDs::save, CommandIDs::saveAs,
160172
CommandIDs::undo, CommandIDs::redo, CommandIDs::login,
161-
CommandIDs::about
173+
CommandIDs::about, CommandIDs::settings
162174
};
163175
commands.addArray(ids, numElementsInArray(ids));
164176
}
@@ -199,12 +211,16 @@ class MainComponent : public Component,
199211
case CommandIDs::about:
200212
result.setInfo("About HARP", "Shows information about the application", "About", 0);
201213
break;
214+
case CommandIDs::settings:
215+
result.setInfo("Preferences...", "Open the settings window", "Settings", 0);
216+
break;
202217
}
203218
}
204219

205220
// Callback for the save and saveAs commands
206221
bool perform(const InvocationInfo& info) override
207222
{
223+
DBG("perform() called");
208224
switch (info.commandID)
209225
{
210226
case CommandIDs::open:
@@ -235,6 +251,10 @@ class MainComponent : public Component,
235251
DBG("About command invoked");
236252
showAboutDialog();
237253
break;
254+
case CommandIDs::settings:
255+
DBG("Settings command invoked");
256+
showSettingsDialog();
257+
break;
238258
default:
239259
return false;
240260
}
@@ -733,6 +753,21 @@ class MainComponent : public Component,
733753
MessageManager::callAsync([this, loadingError]
734754
{ loadModelButton.setEnabled(false); });
735755
}
756+
if (loadingError.userMessage.containsIgnoreCase("sleeping"))
757+
{
758+
MessageManager::callAsync([this] {
759+
addCustomPathToDropdown(customPath, true); // mark as sleeping
760+
});
761+
}
762+
//NEW: reopen custom path dialog if sleeping or 404
763+
if (loadingError.type == ErrorType::InvalidURL ||
764+
loadingError.devMessage.contains("404") ||
765+
loadingError.userMessage.containsIgnoreCase("sleeping"))
766+
{
767+
MessageManager::callAsync([this] {
768+
openCustomPathDialog(customPath);
769+
});
770+
}
736771
};
737772

738773
AlertWindow::showAsync(msgOpts, alertCallback);
@@ -756,6 +791,31 @@ class MainComponent : public Component,
756791
});
757792
}
758793

794+
void openCustomPathDialog(const std::string& prefillPath = "")
795+
{
796+
std::function<void(const juce::String&)> loadCallback =
797+
[this](const juce::String& customPath2)
798+
{
799+
this->customPath = customPath2.toStdString();
800+
loadModelButton.triggerClick(); // Trigger load
801+
};
802+
803+
std::function<void()> cancelCallback = [this]()
804+
{
805+
if (lastLoadedModelItemIndex != -1)
806+
modelPathComboBox.setSelectedId(lastLoadedModelItemIndex + 1);
807+
else if (lastSelectedItemIndex != -1)
808+
modelPathComboBox.setSelectedId(lastSelectedItemIndex + 1);
809+
else
810+
resetModelPathComboBox();
811+
};
812+
813+
CustomPathDialog* dialog = new CustomPathDialog(loadCallback, cancelCallback);
814+
if (!prefillPath.empty())
815+
dialog->setTextFieldValue(prefillPath);
816+
}
817+
818+
759819
void resetModelPathComboBox()
760820
{
761821
// cb: why do we resetUI inside a function named resetModelPathComboBox ?
@@ -784,6 +844,36 @@ class MainComponent : public Component,
784844
lastSelectedItemIndex = -1;
785845
}
786846

847+
// Adds a path to the model dropdown if it's not already present
848+
void addCustomPathToDropdown(const std::string& path, bool wasSleeping = false)
849+
{
850+
juce::String displayStr(path);
851+
if (wasSleeping)
852+
displayStr += " (sleeping)";
853+
854+
bool alreadyExists = false;
855+
for (int i = 0; i < modelPathComboBox.getNumItems(); ++i)
856+
{
857+
if (modelPathComboBox.getItemText(i).startsWithIgnoreCase(path))
858+
{
859+
alreadyExists = true;
860+
break;
861+
}
862+
}
863+
864+
if (!alreadyExists)
865+
{
866+
int newID = modelPathComboBox.getNumItems() + 1;
867+
modelPathComboBox.addItem(displayStr, newID);
868+
}
869+
870+
modelPathComboBox.setText(displayStr, juce::dontSendNotification);
871+
}
872+
873+
874+
875+
876+
787877
void focusCallback()
788878
{
789879
// if (mediaDisplay->isFileLoaded())
@@ -825,6 +915,20 @@ class MainComponent : public Component,
825915
// }
826916
}
827917

918+
void showSettingsDialog()
919+
{
920+
DBG("Settings command invoked");
921+
922+
juce::DialogWindow::LaunchOptions options;
923+
options.dialogTitle = "Settings";
924+
options.content.setOwned(new SettingsBox());
925+
options.useNativeTitleBar = true;
926+
options.resizable = true;
927+
options.escapeKeyTriggersCloseButton = true;
928+
options.dialogBackgroundColour = juce::Colours::lightgrey;
929+
options.launchAsync();
930+
}
931+
828932
void initMenuBar()
829933
{
830934
// init the menu bar
@@ -833,13 +937,17 @@ class MainComponent : public Component,
833937
setApplicationCommandManagerToWatch(&commandManager);
834938
// Register commands
835939
commandManager.registerAllCommandsForTarget(this);
940+
commandManager.setFirstCommandTarget(this);
941+
836942
// commandManager.setFirstCommandTarget(this);
837943
addKeyListener(commandManager.getKeyMappings());
838944

839945
#if JUCE_MAC
840946
// Not used for now
841-
// auto extraMenu = getMacExtraMenu();
842-
MenuBarModel::setMacMainMenu(this);
947+
//auto extraMenu = getMacExtraMenu();
948+
// MenuBarModel::setMacMainMenu(this);
949+
macExtraMenu = getMacExtraMenu();
950+
MenuBarModel::setMacMainMenu(this, macExtraMenu.get());
843951
#endif
844952

845953
menuBar->setVisible(true);
@@ -1116,6 +1224,7 @@ class MainComponent : public Component,
11161224
// set to full screen
11171225
// setFullScreen(true);
11181226
resized();
1227+
11191228
}
11201229

11211230
~MainComponent() override
@@ -1478,8 +1587,7 @@ class MainComponent : public Component,
14781587
menuBar->setBounds(
14791588
area.removeFromTop(LookAndFeel::getDefaultLookAndFeel().getDefaultMenuBarHeight()));
14801589
#endif
1481-
1482-
auto margin = 2; // Adjusted margin value for top and bottom spacing
1590+
auto margin = 2; // Adjusted margin value for top and bottom spacing
14831591

14841592
// Create a FlexBox container
14851593
juce::FlexBox flexBox;
@@ -1590,6 +1698,7 @@ class MainComponent : public Component,
15901698
std::unique_ptr<ModelStatusTimer> mModelStatusTimer { nullptr };
15911699

15921700
ComboBox modelPathComboBox;
1701+
15931702
// Two usefull variables to keep track of the selected item in the modelPathComboBox
15941703
// and the item index of the last loaded model
15951704
// These are used to restore the selected item in the modelPathComboBox
@@ -1680,6 +1789,7 @@ class MainComponent : public Component,
16801789

16811790
std::shared_ptr<fontawesome::IconHelper> fontawesomeHelper;
16821791
std::shared_ptr<fontaudio::IconHelper> fontaudioHelper;
1792+
std::unique_ptr<PopupMenu> macExtraMenu;
16831793

16841794
void play()
16851795
{

src/gui/CustomPathDialog.h

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ inline Colour getUIColourIfAvailable(LookAndFeel_V4::ColourScheme::UIColour uiCo
1818
return fallback;
1919
}
2020

21-
class CustomPathComponent : public Component
21+
class CustomPathComponent : public Component, public juce::TextEditor::Listener
2222
{
2323
public:
2424
CustomPathComponent(std::function<void(const String&)> onLoadCallback,
@@ -31,6 +31,8 @@ class CustomPathComponent : public Component
3131
customPathEditor.onTextChange = [this]()
3232
{ loadButton.setEnabled(customPathEditor.getText().isNotEmpty()); };
3333

34+
customPathEditor.addListener(this); //listen for the enter key press
35+
3436
// Set up the Load button
3537
addAndMakeVisible(loadButton);
3638
loadButton.setButtonText("Load");
@@ -69,9 +71,23 @@ class CustomPathComponent : public Component
6971
g.fillAll(getUIColourIfAvailable(LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
7072
}
7173

74+
void textEditorReturnKeyPressed(juce::TextEditor&) override
75+
{
76+
if (loadButton.isEnabled())
77+
loadButton.triggerClick();
78+
}
79+
80+
void setTextFieldValue(const std::string& path)
81+
{
82+
customPathEditor.setText(path, juce::dontSendNotification);
83+
customPathEditor.selectAll();
84+
}
85+
86+
7287
private:
7388
TextEditor customPathEditor;
7489
TextButton loadButton, cancelButton;
90+
CustomPathComponent* pathComponent = nullptr;
7591
};
7692

7793
class CustomPathDialog : public DialogWindow
@@ -84,12 +100,18 @@ class CustomPathDialog : public DialogWindow
84100
m_onCancelCallback(onCancelCallback)
85101
{
86102
// Create the content component
87-
auto* content =
88-
new CustomPathComponent([this](const String& path) { loadButtonPressed(path); },
89-
[this]() { cancelButtonPressed(); });
103+
//auto* content =
104+
// new CustomPathComponent([this](const String& path) { loadButtonPressed(path); },
105+
//[this]() { cancelButtonPressed(); });
106+
pathComponent = new CustomPathComponent(
107+
[this](const String& path) { loadButtonPressed(path); },
108+
[this]() { cancelButtonPressed(); });
109+
110+
setContentOwned(pathComponent, true);
111+
90112

91113
// Add custom content
92-
setContentOwned(content, true);
114+
//setContentOwned(content, true);
93115

94116
// Other dialog window options
95117
setUsingNativeTitleBar(false);
@@ -131,7 +153,15 @@ class CustomPathDialog : public DialogWindow
131153
delete this;
132154
}
133155

156+
void setTextFieldValue(const std::string& path)
157+
{
158+
if (pathComponent != nullptr)
159+
pathComponent->setTextFieldValue(path);
160+
}
161+
162+
134163
private:
135164
std::function<void(const String&)> m_onLoadCallback;
136165
std::function<void()> m_onCancelCallback;
166+
CustomPathComponent* pathComponent = nullptr;
137167
};

0 commit comments

Comments
 (0)