Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,12 @@ target_sources(${PROJECT_NAME}
src/gui/HoverHandler.h
src/gui/ControlComponent.h
src/gui/ToggleWithLabel.h
src/gui/FileChooserWithLabel.h

src/media/MediaDisplayComponent.cpp
src/media/AudioDisplayComponent.cpp
src/media/MidiDisplayComponent.cpp
src/media/FileDisplayComponent.cpp
src/media/OutputLabelComponent.cpp

src/media/pianoroll/KeyboardComponent.cpp
Expand Down
2 changes: 1 addition & 1 deletion pyharp
65 changes: 65 additions & 0 deletions src/Model.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,31 @@ class Model
}
}

std::map<Uuid, std::string> fileControlRemotePaths;

for (const auto& controlComponent : controlComponents)
{
if (auto* fileComponentInfo = dynamic_cast<FileComponentInfo*>(controlComponent.get()))
{
if (fileComponentInfo->path.empty())
continue;

String remoteFilePath;

result =
client->uploadFile(loadedPath, File(fileComponentInfo->path), remoteFilePath);

if (result.failed())
{
setStatus(ModelStatus::FAILURE);

return result;
}

fileControlRemotePaths[fileComponentInfo->id] = remoteFilePath.toStdString();
}
}

/* Extract control values for JSON payload in order */

Array<var> controlValues;
Expand Down Expand Up @@ -314,6 +339,25 @@ class Model
{
controlValue = var(comboBoxComponentInfo->value);
}
else if (auto fileComponentInfo = dynamic_cast<FileComponentInfo*>(componentInfo.get()))
{
auto it = fileControlRemotePaths.find(fileComponentInfo->id);

if (it == fileControlRemotePaths.end() || it->second.empty())
{
controlValue = var();
}
else
{
DynamicObject::Ptr fileObj = new DynamicObject();

fileObj->setProperty("path", var(it->second));

controlValue = var(fileObj);
}

wasFile = true;
}
else
{
// Unsupported control was added
Expand Down Expand Up @@ -440,6 +484,17 @@ class Model
DBG_AND_LOG("Model::extractInputs: MIDI track input \"" + midiTrack->label
+ "\" extracted.");
}
else if (type == "generic_file")
{
std::shared_ptr<ModelComponentInfo> fileChooser =
std::make_shared<FileComponentInfo>(controlsDict);

newControls.push_back(fileChooser);
tempComponentIDs.push_back(fileChooser->id);

DBG_AND_LOG("Model::extractInputs: File chooser control \"" + fileChooser->label
+ "\" extracted.");
}
else if (type == "text_box")
{
std::shared_ptr<ModelComponentInfo> textControl =
Expand Down Expand Up @@ -563,6 +618,16 @@ class Model
DBG_AND_LOG("Model::extractOutputs: MIDI track output \"" + midiTrack->label
+ "\" extracted.");
}
else if (type == "generic_file")
{
std::shared_ptr<ModelComponentInfo> fileOutput =
std::make_shared<FileComponentInfo>(controlsDict);

newOutputs.push_back(fileOutput);

DBG_AND_LOG("Model::extractOutputs: File output \"" + fileOutput->label
+ "\" extracted.");
}
else if (type == "json")
{
// Labels are handled separately
Expand Down
21 changes: 20 additions & 1 deletion src/ModelTab.h
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,23 @@ class ModelTab : public Component, private ChangeListener, public ChangeBroadcas
}
}

for (const auto& controlInfo : model->getControls())
{
if (auto* fileInfo = dynamic_cast<FileComponentInfo*>(controlInfo.get()))
{
if (fileInfo->required && fileInfo->path.empty())
{
AlertWindow::showMessageBoxAsync(
AlertWindow::WarningIcon,
"Error",
"Required file input \"" + String(fileInfo->label)
+ "\" is empty. Please select a file before processing.");

return;
}
}
}

modelSelectionWidget.setDisabled();
processCancelButton.setMode(cancelButtonInfo.displayLabel);

Expand Down Expand Up @@ -536,7 +553,9 @@ class ModelTab : public Component, private ChangeListener, public ChangeBroadcas
{
auto& outputMediaDisplays = outputTrackAreaWidget.getMediaDisplays();

for (size_t i = 0; i < outputMediaDisplays.size(); ++i)
for (size_t i = 0;
i < outputMediaDisplays.size() && i < outputFilesPtr->size();
++i)
{
outputMediaDisplays[i]->initializeDisplay(
URL((*outputFilesPtr)[i]));
Expand Down
204 changes: 204 additions & 0 deletions src/gui/FileChooserWithLabel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/**
* @file FileChooserWithLabel.h
* @brief Custom file chooser component with label.
* @author derekllanes, cwitkowitz
*/

#pragma once

#include <string>
#include <vector>

#include "ControlComponent.h"
#include "MultiButton.h"

using namespace juce;

class FileChooserWithLabel : public ControlComponent, public FileDragAndDropTarget
{
public:
~FileChooserWithLabel() { actionButton.setLookAndFeel(nullptr); }

FileChooserWithLabel(const String& labelText = {})
{
label.setText(labelText, dontSendNotification);
label.setJustificationType(Justification::centred);
addAndMakeVisible(label);

initializeButton();
addAndMakeVisible(actionButton);
}

void resized() override
{
auto area = getLocalBounds();
label.setBounds(area.removeFromTop(labelHeight));
int buttonSize = area.getHeight();
actionButton.setBounds(area.removeFromRight(buttonSize));
}

void paint(Graphics& g) override
{
auto body = getLocalBounds().withTrimmedTop(labelHeight).toFloat();
auto bodyInt = body.toNearestInt();
float r = 3.0f;

auto bg = findColour(ComboBox::backgroundColourId);
auto outline = findColour(ComboBox::outlineColourId);
auto textCol = findColour(ComboBox::textColourId);

g.setColour(bg);
g.fillRoundedRectangle(body.reduced(0.5f), r);
g.setColour(outline);
g.drawRoundedRectangle(body.reduced(0.5f), r, 1.0f);

auto textArea = bodyInt.withTrimmedRight(bodyInt.getHeight()).withTrimmedLeft(4);

g.setFont(Font(13.0f));
g.setColour(currentPath.isEmpty() ? textCol.withAlpha(0.4f) : textCol.withAlpha(0.85f));
g.drawText(currentPath.isEmpty() ? getPlaceholderText() : File(currentPath).getFileName(),
textArea,
Justification::centredLeft,
true);
}

bool isInterestedInFileDrag(const StringArray&) override { return currentPath.isEmpty(); }

void filesDropped(const StringArray& files, int, int) override
{
if (! files.isEmpty())
{
File f(files[0]);

if (f.existsAsFile())
setAndNotify(f.getFullPathName());
}
}

void setPath(const String& path)
{
currentPath = path;
actionButton.setMode(currentPath.isEmpty() ? chooseFileModeInfo.displayLabel
: removeFileModeInfo.displayLabel);
repaint();
}

void setFileTypes(const std::vector<std::string>& types) { fileTypes = types; }

int getMinimumRequiredWidth() const override
{
const int labelWidth = getLabelWidth(label);
return jmax(minFilePickerWidth, labelWidth + defaultPadding);
}

std::function<void(const String&)> onFileSelected;

private:
void initializeButton()
{
chooseFileModeInfo = MultiButton::Mode { "ChooseFile",
"Click to choose a file.",
[this] { launchFileChooser(); },
MultiButton::DrawingMode::IconOnly,
Colours::lightblue,
fontawesome::Folder };

removeFileModeInfo = MultiButton::Mode { "RemoveFile",
"Click to remove the selected file.",
[this] { clearFile(); },
MultiButton::DrawingMode::IconOnly,
Colours::orangered,
fontawesome::Remove };

actionButton.addMode(chooseFileModeInfo);
actionButton.addMode(removeFileModeInfo);
actionButton.setMode(chooseFileModeInfo.displayLabel);

actionButton.setLookAndFeel(&noBorderLAF);
}

String getPlaceholderText() const
{
if (fileTypes.empty())
return "No file selected";

StringArray exts;

for (const auto& ext : fileTypes)
{
String e(ext);
exts.add(e.startsWithChar('.') ? e : "." + e);
}

return "No file selected (" + exts.joinIntoString(", ") + ")";
}

void clearFile()
{
setPath({});

if (onFileSelected)
onFileSelected({});
}

void setAndNotify(const String& path)
{
setPath(path);

if (onFileSelected)
onFileSelected(path);
}

void launchFileChooser()
{
fileChooser = std::make_unique<FileChooser>("Select file", File(), buildWildcardPattern());

fileChooser->launchAsync(FileBrowserComponent::openMode
| FileBrowserComponent::canSelectFiles,
[this](const FileChooser& chooser)
{
const File f = chooser.getResult();

if (f.existsAsFile())
setAndNotify(f.getFullPathName());
});
}

String buildWildcardPattern() const
{
if (fileTypes.empty())
return "*";

StringArray patterns;

for (const auto& ext : fileTypes)
{
String e(ext);

if (! e.startsWithChar('.'))
e = "." + e;

patterns.add("*" + e);
}

return patterns.joinIntoString(";");
}

struct NoBorderLookAndFeel : public LookAndFeel_V4
{
void drawButtonBackground(Graphics&, Button&, const Colour&, bool, bool) override {}
};

static constexpr int minFilePickerWidth = 260;
static constexpr int labelHeight = 20;

NoBorderLookAndFeel noBorderLAF;
Label label;
MultiButton actionButton;
MultiButton::Mode chooseFileModeInfo;
MultiButton::Mode removeFileModeInfo;

String currentPath;
std::vector<std::string> fileTypes;
std::unique_ptr<FileChooser> fileChooser;
};
1 change: 1 addition & 0 deletions src/gui/TextBoxWithLabel.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class TextBoxWithLabel : public ControlComponent
TextBoxWithLabel(const String& labelText)
{
label.setText(labelText, dontSendNotification);
label.setJustificationType(Justification::centred);

textBox.setMultiLine(true, true);
textBox.setReadOnly(false);
Expand Down
Loading
Loading