2018-02-27 08:25:20 +08:00
|
|
|
//
|
|
|
|
// Model.cpp
|
|
|
|
// Lazarus
|
|
|
|
//
|
|
|
|
// Created by Alex Birch on 01/09/2017.
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
|
|
|
#include "TableComponent.h"
|
2019-07-14 05:37:26 +08:00
|
|
|
#include "Util.h"
|
2019-07-21 01:49:39 +08:00
|
|
|
#include <functional>
|
|
|
|
#include <iterator>
|
|
|
|
#include <map>
|
2018-02-27 08:25:20 +08:00
|
|
|
|
|
|
|
using namespace std;
|
2019-07-14 05:37:26 +08:00
|
|
|
using namespace Util;
|
2018-02-27 08:25:20 +08:00
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
/**
|
|
|
|
This class shows how to implement a TableListBoxModel to show in a TableListBox.
|
|
|
|
*/
|
|
|
|
TableComponent::TableComponent(
|
2019-07-15 00:22:36 +08:00
|
|
|
AudioProcessorValueTreeState& valueTreeState
|
2018-02-27 08:25:20 +08:00
|
|
|
)
|
2019-07-14 05:37:26 +08:00
|
|
|
: valueTreeState{valueTreeState}
|
|
|
|
, font{14.0f}
|
2018-02-27 08:25:20 +08:00
|
|
|
{
|
|
|
|
// Create our table component and add it to this component..
|
|
|
|
addAndMakeVisible (table);
|
|
|
|
table.setModel (this);
|
|
|
|
|
|
|
|
// give it a border
|
|
|
|
table.setColour (ListBox::outlineColourId, Colours::grey);
|
|
|
|
table.setOutlineThickness (1);
|
|
|
|
|
|
|
|
int columnIx = 1;
|
|
|
|
|
2019-07-13 07:16:35 +08:00
|
|
|
table.getHeader().addColumn (
|
|
|
|
String("#"),
|
|
|
|
columnIx++,
|
|
|
|
30, // column width
|
|
|
|
30, // min width
|
|
|
|
400, // max width
|
|
|
|
TableHeaderComponent::defaultFlags
|
|
|
|
);
|
|
|
|
table.getHeader().addColumn (
|
|
|
|
String("Name"),
|
|
|
|
columnIx++,
|
|
|
|
200, // column width
|
|
|
|
30, // min width
|
|
|
|
400, // max width
|
|
|
|
TableHeaderComponent::defaultFlags
|
|
|
|
);
|
2018-02-27 08:25:20 +08:00
|
|
|
|
|
|
|
table.setWantsKeyboardFocus(false);
|
|
|
|
|
2019-07-21 01:49:39 +08:00
|
|
|
ValueTree banks{valueTreeState.state.getChildWithName("banks")};
|
|
|
|
loadModelFrom(banks);
|
2018-02-27 08:25:20 +08:00
|
|
|
|
|
|
|
// we could now change some initial settings..
|
2019-07-13 07:16:35 +08:00
|
|
|
table.getHeader().setSortColumnId(1, false); // sort ascending by ID column
|
|
|
|
valueTreeState.state.addListener(this);
|
2019-07-21 02:56:12 +08:00
|
|
|
valueTreeState.addParameterListener("bank", this);
|
|
|
|
valueTreeState.addParameterListener("preset", this);
|
2019-07-13 07:16:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
TableComponent::~TableComponent() {
|
2019-07-21 02:56:12 +08:00
|
|
|
valueTreeState.removeParameterListener("bank", this);
|
|
|
|
valueTreeState.removeParameterListener("preset", this);
|
2019-07-13 07:16:35 +08:00
|
|
|
valueTreeState.state.removeListener(this);
|
|
|
|
}
|
|
|
|
|
2019-07-21 01:49:39 +08:00
|
|
|
void TableComponent::loadModelFrom(ValueTree& banks) {
|
|
|
|
banksToPresets.clear();
|
|
|
|
int banksChildren{banks.getNumChildren()};
|
|
|
|
for(int bankIx{0}; bankIx<banksChildren; bankIx++) {
|
|
|
|
ValueTree bank{banks.getChild(bankIx)};
|
|
|
|
int bankNum{bank.getProperty("num")};
|
|
|
|
int bankChildren{bank.getNumChildren()};
|
|
|
|
for(int presetIx{0}; presetIx<bankChildren; presetIx++) {
|
|
|
|
ValueTree preset{bank.getChild(presetIx)};
|
|
|
|
int presetNum{preset.getProperty("num")};
|
|
|
|
String presetName{preset.getProperty("name")};
|
|
|
|
TableRow row{presetNum, move(presetName)};
|
|
|
|
banksToPresets.emplace(bankNum, move(row));
|
|
|
|
}
|
2019-07-14 21:19:27 +08:00
|
|
|
}
|
2019-07-21 02:56:12 +08:00
|
|
|
repopulateTable();
|
2019-07-14 21:19:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void TableComponent::parameterChanged(const String& parameterID, float newValue) {
|
2019-07-21 02:56:12 +08:00
|
|
|
if (parameterID == "bank") {
|
|
|
|
repopulateTable();
|
|
|
|
} else if (parameterID == "preset") {
|
2019-07-14 21:19:27 +08:00
|
|
|
selectCurrentPreset();
|
|
|
|
}
|
|
|
|
}
|
2019-07-13 07:16:35 +08:00
|
|
|
|
2019-07-21 02:56:12 +08:00
|
|
|
void TableComponent::repopulateTable() {
|
|
|
|
rows.clear();
|
|
|
|
RangedAudioParameter *param{valueTreeState.getParameter("bank")};
|
|
|
|
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
|
|
|
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
|
|
|
int bank{castParam->get()};
|
|
|
|
|
|
|
|
BanksToPresets::iterator lowerBound{banksToPresets.lower_bound(bank)};
|
|
|
|
BanksToPresets::iterator upperBound{banksToPresets.upper_bound(bank)};
|
|
|
|
|
|
|
|
// basic syntaxes for a lambda which return's a pair's .second
|
|
|
|
// https://stackoverflow.com/questions/2568194/populate-a-vector-with-all-multimap-values-with-a-given-key
|
|
|
|
// shorter syntax with mem_fn()
|
|
|
|
// https://stackoverflow.com/a/36775400/5257399
|
|
|
|
transform(
|
|
|
|
lowerBound,
|
|
|
|
upperBound,
|
|
|
|
back_inserter(rows),
|
|
|
|
mem_fn(&BanksToPresets::value_type::second)
|
|
|
|
);
|
|
|
|
table.deselectAllRows();
|
|
|
|
table.updateContent();
|
|
|
|
table.getHeader().setSortColumnId(0, true);
|
|
|
|
selectCurrentPreset();
|
|
|
|
table.repaint();
|
|
|
|
}
|
|
|
|
|
2019-07-15 02:31:05 +08:00
|
|
|
void TableComponent::valueTreePropertyChanged(
|
|
|
|
ValueTree& treeWhosePropertyHasChanged,
|
|
|
|
const Identifier& property) {
|
2019-07-21 01:49:39 +08:00
|
|
|
if (treeWhosePropertyHasChanged.getType() == StringRef("banks")) {
|
2019-07-15 02:31:05 +08:00
|
|
|
if (property == StringRef("synthetic")) {
|
|
|
|
loadModelFrom(treeWhosePropertyHasChanged);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-27 08:25:20 +08:00
|
|
|
// This is overloaded from TableListBoxModel, and must return the total number of rows in our table
|
|
|
|
int TableComponent::getNumRows()
|
|
|
|
{
|
|
|
|
return static_cast<int>(rows.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is overloaded from TableListBoxModel, and should fill in the background of the whole row
|
|
|
|
void TableComponent::paintRowBackground (
|
|
|
|
Graphics& g,
|
|
|
|
int rowNumber,
|
|
|
|
int /*width*/,
|
|
|
|
int /*height*/,
|
|
|
|
bool rowIsSelected
|
|
|
|
) {
|
|
|
|
const Colour alternateColour (getLookAndFeel().findColour (ListBox::backgroundColourId)
|
|
|
|
.interpolatedWith (getLookAndFeel().findColour (ListBox::textColourId), 0.03f));
|
|
|
|
if (rowIsSelected)
|
|
|
|
g.fillAll (Colours::lightblue);
|
|
|
|
else if (rowNumber % 2)
|
|
|
|
g.fillAll (alternateColour);
|
|
|
|
}
|
|
|
|
|
2019-07-14 21:19:27 +08:00
|
|
|
String TableRow::getStringContents(int columnId) {
|
|
|
|
if (columnId <= 1) {
|
2019-07-15 00:22:36 +08:00
|
|
|
return String(preset);
|
2019-07-14 21:19:27 +08:00
|
|
|
}
|
2019-07-15 00:22:36 +08:00
|
|
|
return name;
|
2019-07-14 21:19:27 +08:00
|
|
|
}
|
|
|
|
|
2018-02-27 08:25:20 +08:00
|
|
|
// This is overloaded from TableListBoxModel, and must paint any cells that aren't using custom
|
|
|
|
// components.
|
|
|
|
void TableComponent::paintCell (
|
|
|
|
Graphics& g,
|
|
|
|
int rowNumber,
|
|
|
|
int columnId,
|
|
|
|
int width,
|
|
|
|
int height,
|
|
|
|
bool /*rowIsSelected*/
|
|
|
|
) {
|
|
|
|
g.setColour (getLookAndFeel().findColour (ListBox::textColourId));
|
|
|
|
g.setFont (font);
|
|
|
|
|
2019-07-14 05:37:26 +08:00
|
|
|
TableRow& row{rows[rowNumber]};
|
2019-07-14 21:19:27 +08:00
|
|
|
String text{row.getStringContents(columnId)};
|
2019-07-14 05:37:26 +08:00
|
|
|
g.drawText (text, 2, 0, width - 4, height, Justification::centredLeft, true);
|
2018-02-27 08:25:20 +08:00
|
|
|
|
|
|
|
g.setColour (getLookAndFeel().findColour (ListBox::backgroundColourId));
|
|
|
|
g.fillRect (width - 1, 0, 1, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is overloaded from TableListBoxModel, and tells us that the user has clicked a table header
|
|
|
|
// to change the sort order.
|
|
|
|
void TableComponent::sortOrderChanged (
|
|
|
|
int newSortColumnId,
|
|
|
|
bool isForwards
|
|
|
|
) {
|
|
|
|
if (newSortColumnId != 0) {
|
|
|
|
TableComponent::DataSorter sorter (newSortColumnId, isForwards);
|
|
|
|
sort(rows.begin(), rows.end(), sorter);
|
|
|
|
|
|
|
|
table.updateContent();
|
2019-07-14 21:19:27 +08:00
|
|
|
selectCurrentPreset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TableComponent::selectCurrentPreset() {
|
|
|
|
table.deselectAllRows();
|
2019-07-31 04:36:54 +08:00
|
|
|
RangedAudioParameter *param{valueTreeState.getParameter("preset")};
|
|
|
|
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
|
|
|
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
2019-07-14 21:19:27 +08:00
|
|
|
int value{castParam->get()};
|
2018-02-27 08:25:20 +08:00
|
|
|
|
2019-07-31 04:36:54 +08:00
|
|
|
for (auto it{rows.begin()}; it != rows.end(); ++it) {
|
2019-07-14 21:19:27 +08:00
|
|
|
if(it->preset == value) {
|
2019-07-31 04:36:54 +08:00
|
|
|
int index{static_cast<int>(distance(rows.begin(), it))};
|
2019-07-14 21:19:27 +08:00
|
|
|
table.selectRow(index);
|
|
|
|
break;
|
2018-02-27 08:25:20 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is overloaded from TableListBoxModel, and should choose the best width for the specified
|
|
|
|
// column.
|
|
|
|
int TableComponent::getColumnAutoSizeWidth (int columnId) {
|
2019-06-30 18:39:39 +08:00
|
|
|
if (columnId == 1)
|
2019-07-13 07:16:35 +08:00
|
|
|
return 30;
|
2018-02-27 08:25:20 +08:00
|
|
|
|
2019-06-30 18:39:39 +08:00
|
|
|
|
2018-02-27 08:25:20 +08:00
|
|
|
int widest = 32;
|
|
|
|
|
|
|
|
// find the widest bit of text in this column..
|
2019-07-14 05:37:26 +08:00
|
|
|
for (int i{getNumRows()}; --i >= 0;) {
|
|
|
|
TableRow& row{rows[i]};
|
2019-07-14 21:19:27 +08:00
|
|
|
String text{row.getStringContents(columnId)};
|
2019-07-14 05:37:26 +08:00
|
|
|
widest = jmax (widest, font.getStringWidth (text));
|
2018-02-27 08:25:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return widest + 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
void TableComponent::resized() {
|
|
|
|
// position our table with a gap around its edge
|
|
|
|
table.setBoundsInset (BorderSize<int> (7));
|
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
// A comparator used to sort our data when the user clicks a column header
|
|
|
|
|
|
|
|
TableComponent::DataSorter::DataSorter (
|
|
|
|
int columnByWhichToSort,
|
|
|
|
bool forwards
|
|
|
|
)
|
2019-07-14 05:37:26 +08:00
|
|
|
: columnByWhichToSort (columnByWhichToSort)
|
|
|
|
, direction (forwards ? 1 : -1)
|
2018-02-27 08:25:20 +08:00
|
|
|
{}
|
|
|
|
|
|
|
|
bool TableComponent::DataSorter::operator ()(
|
2019-07-14 05:37:26 +08:00
|
|
|
TableRow first,
|
|
|
|
TableRow second
|
2018-02-27 08:25:20 +08:00
|
|
|
) {
|
2019-07-14 05:37:26 +08:00
|
|
|
int result;
|
|
|
|
if (columnByWhichToSort <= 1) {
|
|
|
|
result = compare(first.preset, second.preset);
|
|
|
|
} else {
|
|
|
|
result = first.name
|
|
|
|
.compareNatural (second.name);
|
|
|
|
if (result == 0) {
|
|
|
|
result = compare(first.preset, second.preset);
|
|
|
|
}
|
|
|
|
}
|
2018-02-27 08:25:20 +08:00
|
|
|
|
|
|
|
result *= direction;
|
|
|
|
|
|
|
|
return result > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TableComponent::selectedRowsChanged (int row) {
|
|
|
|
if (row < 0) {
|
|
|
|
return;
|
|
|
|
}
|
2019-07-14 05:37:26 +08:00
|
|
|
int newPreset{rows[row].preset};
|
2019-07-31 04:36:54 +08:00
|
|
|
RangedAudioParameter *param{valueTreeState.getParameter("preset")};
|
|
|
|
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
|
|
|
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
2019-07-13 07:16:35 +08:00
|
|
|
*castParam = newPreset;
|
2018-02-27 08:25:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool TableComponent::keyPressed(const KeyPress &key) {
|
|
|
|
return table.keyPressed(key);
|
2019-06-30 18:39:39 +08:00
|
|
|
}
|
2019-07-14 05:37:26 +08:00
|
|
|
|
|
|
|
TableRow::TableRow(
|
|
|
|
int preset,
|
|
|
|
String name
|
|
|
|
)
|
|
|
|
: preset{preset}
|
2019-07-14 21:19:27 +08:00
|
|
|
, name{name}
|
2019-07-14 05:37:26 +08:00
|
|
|
{}
|