Skip to content

Commit a8d92e4

Browse files
drowaudioclaude
andcommitted
Merge v2 (drag-and-drop validation) into the CLI11 refactor branch
Brings in the drag-and-drop validation feature (#170 + follow-ups) that landed on develop after this branch was created. Only conflict was Source/CommandLine.cpp: develop renamed a lambda parameter (args -> commandArgs) inside the old performCommandLine/ConsoleApplication block that this refactor deleted, so the change is moot. Resolved by keeping the refactored CommandLine.cpp. The drag-and-drop changes (MainComponent.*, PluginvalLookAndFeel.h) and the CHANGELIST entry merged cleanly. Verified: builds (incl. MainComponent drag-and-drop) and --run-tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 parents 6279379 + 4c5adc2 commit a8d92e4

6 files changed

Lines changed: 180 additions & 12 deletions

File tree

CHANGELIST.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- Enabled the editor stress and extreme (real-time allocation / oversized block) tests that were present in the source tree but had not been compiled into the build
1313

1414
### 1.0.5
15+
- Added drag-and-drop of plug-in files onto the main window, with two drop zones to either validate the plug-in or add it to the plugin list [#170]
1516
- Added static linking to the Windows runtime so it should run on more Windows systems (particularly non-dev machines)
1617
- Made `PluginInfoTest` run on the message thread as the functions it calls aren't thread-safe
1718

ROADMAP.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ There are lots of ways pluginval can be improved, some of these are listed below
44

55
You can help get there by issuing PRs or [funding](FUNDING.md) development.
66

7-
- #### Integration of real-time safety checking (via RTsan or rtcheck)
8-
- #### Improved stack trace/crash reporting
9-
- #### Acceptance testing using input files and reference output files
10-
- #### Automatic integration of Asan/Tsan on platforms that allow it
11-
- #### More tests/logging
12-
- #### Integration with CTest
13-
- #### Improved command-line handling and json config file support
7+
- [x] Integration of real-time safety checking (via rtcheck)
8+
- [ ] Improved command-line handling and json config file support
9+
- [ ] Acceptance testing using input files and reference output files
10+
- [ ] Improved stack trace/crash reporting
11+
- [ ] Automatic integration of Asan/Tsan on platforms that allow it
12+
- [ ] More tests/logging
13+
- [ ] Integration with CTest
14+
- [ ] Integration of real-time safety checking (via RTSan)

Source/MainComponent.cpp

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ MainComponent::MainComponent (Validator& v)
352352
{
353353
strictnessDialog = std::make_unique<StrictnessInfoDialog> (
354354
getStrictnessLevel(),
355-
[this, updateStrictnessButtonText] (int newLevel)
355+
[updateStrictnessButtonText] (int newLevel)
356356
{
357357
setStrictnessLevel (newLevel);
358358
updateStrictnessButtonText();
@@ -382,6 +382,49 @@ void MainComponent::paint (juce::Graphics& g)
382382
g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));
383383
}
384384

385+
void MainComponent::paintOverChildren (juce::Graphics& g)
386+
{
387+
if (dragAction == DropAction::none)
388+
return;
389+
390+
constexpr float cornerRadius = 10.0f;
391+
constexpr float inset = 6.0f;
392+
constexpr float gap = 8.0f;
393+
const auto accent = juce::Colour (0xff4a9eff).withAlpha (0.9f);
394+
395+
auto area = getDragOverlayBounds().toFloat().reduced (inset);
396+
397+
auto leftHalf = area.removeFromLeft ((area.getWidth() - gap) * 0.5f);
398+
area.removeFromLeft (gap);
399+
auto rightHalf = area;
400+
401+
auto drawZone = [&] (juce::Rectangle<float> zone, const juce::String& title,
402+
const juce::String& subtitle, bool active)
403+
{
404+
auto bgCol = accent.withMultipliedSaturation (active ? 1.0f : 0.2f);
405+
g.setColour (bgCol);
406+
g.fillRoundedRectangle (zone, cornerRadius);
407+
408+
g.setColour (accent);
409+
g.drawRoundedRectangle (zone, cornerRadius, active ? 3.0f : 1.5f);
410+
411+
auto textArea = zone.reduced (12.0f);
412+
auto titleArea = textArea.removeFromTop (textArea.getHeight() * 0.5f);
413+
414+
g.setColour (bgCol.contrasting().withAlpha (active ? 1.0f : 0.6f));
415+
g.setFont (juce::Font (juce::FontOptions (24.0f, juce::Font::bold)));
416+
g.drawText (title, titleArea, juce::Justification::centredBottom);
417+
418+
g.setFont (juce::Font (juce::FontOptions (14.0f)));
419+
g.drawText (subtitle, textArea, juce::Justification::centredTop);
420+
};
421+
422+
drawZone (leftHalf, "Validate", "Run validation now",
423+
dragAction == DropAction::validate);
424+
drawZone (rightHalf, "Add to List", "Scan into the plug-in list",
425+
dragAction == DropAction::addToList);
426+
}
427+
385428
void MainComponent::resized()
386429
{
387430
auto r = getLocalBounds();
@@ -419,6 +462,107 @@ void MainComponent::validationStarted (const juce::String&)
419462
tabbedComponent.setCurrentTabIndex (1); // Switch to Console tab
420463
}
421464

465+
//==============================================================================
466+
bool MainComponent::isPluginFile (const juce::String& path)
467+
{
468+
static const juce::StringArray extensions { ".vst3", ".vst", ".component", ".dll", ".so", ".clap" };
469+
const auto lower = path.toLowerCase();
470+
471+
for (const auto& ext : extensions)
472+
if (lower.endsWith (ext))
473+
return true;
474+
475+
return false;
476+
}
477+
478+
bool MainComponent::isInterestedInFileDrag (const juce::StringArray& files)
479+
{
480+
if (validator.isValidating())
481+
return false;
482+
483+
for (const auto& f : files)
484+
if (isPluginFile (f))
485+
return true;
486+
487+
return false;
488+
}
489+
490+
juce::Rectangle<int> MainComponent::getDragOverlayBounds() const
491+
{
492+
return getLocalBounds().withTrimmedTop (menuBar.getBottom());
493+
}
494+
495+
MainComponent::DropAction MainComponent::dropActionForX (int x) const
496+
{
497+
return x < getDragOverlayBounds().getCentreX() ? DropAction::validate
498+
: DropAction::addToList;
499+
}
500+
501+
void MainComponent::updateDragAction (const juce::StringArray& files, int x)
502+
{
503+
const auto newAction = isInterestedInFileDrag (files) ? dropActionForX (x)
504+
: DropAction::none;
505+
506+
if (dragAction != newAction)
507+
{
508+
dragAction = newAction;
509+
repaint();
510+
}
511+
}
512+
513+
void MainComponent::fileDragEnter (const juce::StringArray& files, int x, int)
514+
{
515+
updateDragAction (files, x);
516+
}
517+
518+
void MainComponent::fileDragMove (const juce::StringArray& files, int x, int)
519+
{
520+
updateDragAction (files, x);
521+
}
522+
523+
void MainComponent::fileDragExit (const juce::StringArray&)
524+
{
525+
if (dragAction != DropAction::none)
526+
{
527+
dragAction = DropAction::none;
528+
repaint();
529+
}
530+
}
531+
532+
void MainComponent::filesDropped (const juce::StringArray& files, int x, int)
533+
{
534+
const auto action = dropActionForX (x);
535+
536+
if (dragAction != DropAction::none)
537+
{
538+
dragAction = DropAction::none;
539+
repaint();
540+
}
541+
542+
juce::StringArray pluginFiles;
543+
544+
for (const auto& f : files)
545+
if (isPluginFile (f))
546+
pluginFiles.add (f);
547+
548+
if (pluginFiles.isEmpty())
549+
return;
550+
551+
getAppPreferences().setValue ("lastPluginLocation", pluginFiles[pluginFiles.size() - 1]);
552+
553+
if (action == DropAction::addToList)
554+
{
555+
juce::OwnedArray<juce::PluginDescription> typesFound;
556+
knownPluginList.scanAndAddDragAndDroppedFiles (formatManager, pluginFiles, typesFound);
557+
savePluginList();
558+
}
559+
else
560+
{
561+
validator.setValidateInProcess (getValidateInProcess());
562+
validator.validate (pluginFiles, getTestOptions());
563+
}
564+
}
565+
422566
//==============================================================================
423567
juce::StringArray MainComponent::getMenuBarNames()
424568
{

Source/MainComponent.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ class PluginTableComponent : public juce::Component,
381381
*/
382382
class MainComponent : public juce::Component,
383383
public juce::MenuBarModel,
384+
public juce::FileDragAndDropTarget,
384385
private juce::ChangeListener,
385386
private Validator::Listener
386387
{
@@ -391,6 +392,7 @@ class MainComponent : public juce::Component,
391392

392393
//==============================================================================
393394
void paint (juce::Graphics&) override;
395+
void paintOverChildren (juce::Graphics&) override;
394396
void resized() override;
395397

396398
//==============================================================================
@@ -399,6 +401,14 @@ class MainComponent : public juce::Component,
399401
juce::PopupMenu getMenuForIndex (int menuIndex, const juce::String& menuName) override;
400402
void menuItemSelected (int menuItemID, int topLevelMenuIndex) override;
401403

404+
//==============================================================================
405+
// FileDragAndDropTarget
406+
bool isInterestedInFileDrag (const juce::StringArray& files) override;
407+
void fileDragEnter (const juce::StringArray& files, int x, int y) override;
408+
void fileDragMove (const juce::StringArray& files, int x, int y) override;
409+
void fileDragExit (const juce::StringArray& files) override;
410+
void filesDropped (const juce::StringArray& files, int x, int y) override;
411+
402412
private:
403413
//==============================================================================
404414
Validator& validator;
@@ -418,6 +428,16 @@ class MainComponent : public juce::Component,
418428
StatusBar statusBar { validator };
419429
std::unique_ptr<StrictnessInfoDialog> strictnessDialog;
420430

431+
// Which action a drop will perform, based on which half of the window the
432+
// drag is over. DropAction::none means no plug-in drag is in progress.
433+
enum class DropAction { none, validate, addToList };
434+
DropAction dragAction = DropAction::none;
435+
436+
static bool isPluginFile (const juce::String& path);
437+
juce::Rectangle<int> getDragOverlayBounds() const;
438+
DropAction dropActionForX (int x) const;
439+
void updateDragAction (const juce::StringArray& files, int x);
440+
421441
void savePluginList();
422442
juce::PopupMenu createFileMenu();
423443
juce::PopupMenu createTestMenu();

Source/PluginvalLookAndFeel.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ class PluginvalLookAndFeel : public juce::LookAndFeel_V4
4444
const auto textColour = juce::Colour (0xffe0e0e0);
4545
const auto textDimmed = juce::Colour (0xff909090);
4646
const auto accentColour = juce::Colour (0xffffffff);
47-
const auto highlightColour = getAccentColour().withAlpha (0.3f);
4847

4948
// Window colours
5049
setColour (juce::ResizableWindow::backgroundColourId, backgroundDark);
@@ -194,7 +193,7 @@ class PluginvalLookAndFeel : public juce::LookAndFeel_V4
194193
g.drawEllipse (juce::Rectangle<float> (thumbWidth, thumbWidth).withCentre (thumbPoint), 1.0f);
195194
}
196195

197-
void drawTabButton (juce::TabBarButton& button, juce::Graphics& g, bool isMouseOver, bool isMouseDown) override
196+
void drawTabButton (juce::TabBarButton& button, juce::Graphics& g, bool isMouseOver, bool /*isMouseDown*/) override
198197
{
199198
auto area = button.getActiveArea().toFloat();
200199
auto backgroundColour = findColour (juce::ResizableWindow::backgroundColourId);
@@ -219,7 +218,7 @@ class PluginvalLookAndFeel : public juce::LookAndFeel_V4
219218
g.drawText (button.getButtonText(), area.reduced (12.0f, 0.0f), juce::Justification::centred);
220219
}
221220

222-
int getTabButtonBestWidth (juce::TabBarButton& button, int tabDepth) override
221+
int getTabButtonBestWidth (juce::TabBarButton& button, int /*tabDepth*/) override
223222
{
224223
auto width = juce::GlyphArrangement::getStringWidthInt (juce::Font (juce::FontOptions (14.0f)), button.getButtonText()) + 40; // Extra padding
225224
return juce::jmax (width, 80);
@@ -236,7 +235,7 @@ class PluginvalLookAndFeel : public juce::LookAndFeel_V4
236235
}
237236

238237
void drawTableHeaderColumn (juce::Graphics& g, juce::TableHeaderComponent& header,
239-
const juce::String& columnName, int columnId,
238+
const juce::String& columnName, int /*columnId*/,
240239
int width, int height, bool isMouseOver, bool isMouseDown,
241240
int columnFlags) override
242241
{

Source/Validator.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ class Validator : public juce::ChangeBroadcaster,
8787
/** Validates an array of PluginDescriptions. */
8888
bool validate (const juce::Array<juce::PluginDescription>& pluginsToValidate, PluginTests::Options);
8989

90+
/** Returns true if a validation is currently in progress. */
91+
bool isValidating() const { return multiValidator != nullptr; }
92+
9093
/** Call this to make validation happen in the same process.
9194
This can be useful for debugging but should not generally be used as a crashing
9295
plugin will bring down the app.

0 commit comments

Comments
 (0)