The Red Penguin

Add a component ID and converting between ints and strings


Thursday 6 August 2020

In the last post, we got as far as creating a button listener, which allowed us to trigger a function when the user clicks on one of the buttons in our playlist. If the user clicks on a button it prints out a message.

Now the problem is we have no way currently of identifying the button. So what do we normally do? Well, we look at the address.

We need to convert the ID number from an integer in a string to do this.

1. In the refreshComponentForCell function in PlaylistComponent.cpp add
juce::String id{std::to_string(rowNumber)};
btn->setComponentID(id);
This changes the integer into a standard string and then passes the variable back correctly.

2. We need to convert from a text string back to an integer to get the index in the track list vector. So in PlaylistComponent::buttonClicked we add:
int id = std::stoi(button->getComponentI().toStdString());

This shows we start with a JUCE string, we convert to a standard string using toStdString() and then stoi converts it to an integer.

Then in buttonclicked we can do something like
std::cout << trackTitles[id] << std::endl;
to get the name of the track with the ID index.

*** I didn't really understand a lot of this!

Implement a play button and add a listener


Thursday 6 August 2020

1. We need to initialise a new function in PlaylistComponent.h
    juce::Component* refreshComponentForCell(int rowNumber,
int columnID,
bool isRowSelected,
juce::Component* existingComponentToUpdate)
override;

2. And initialise this in PlaylistComponent.cpp:
juce::Component* PlaylistComponent::refreshComponentForCell(int rowNumber,
int columnID,
bool isRowSelected,
juce::Component* existingComponentToUpdate)
{
return existingComponentToUpdate;
}

3. We need to add a new column for the play buttons, so we need to add this to show the header:
tableComponent.getHeader().addColumn("", 2, 200);

4. Add more code to the new function:
Component* PlaylistComponent::refreshComponentForCell(int rowNumber,
int columnID,
bool isRowSelected,
Component* existingComponentToUpdate)
{
if (columnID == 2)
{
if (existingComponentToUpdate == nullptr)
{
existingComponentToUpdate = new juce::TextButton{ "play" };
}
}
return existingComponentToUpdate;
The play button column has a column ID of 2 so we need to do a test on this column. If the existing component is nullptr (we haven't created it yet) then we have to create it.

5. Now we need to hook up a listener to this button so we can receive events from it.

Go to PlaylistComponent.h and add an extra class inheritance:
class PlaylistComponent  : public juce::Component, 
public juce::TableListBoxModel,
public juce::Button::Listener
(we have this in DeckGUI.h as well)

6. This requires us to implement one function so in public in PlaylistComponent.h we add:
void buttonClicked(juce::Button* button) override;


7. We need to implement the new function in PlaylistComponent.cpp so at the bottom of the .cpp file add:
void PlaylistComponent::buttonClicked(juce::Button* button)
{
(DBG "click");
}
which for now will just show a button click to the console.

8. We need to create a TextButton pointer in juce::Component* PlaylistComponent::refreshComponentForCell so we add a line to make the function look like this:
juce::Component* PlaylistComponent::refreshComponentForCell(int rowNumber,
int columnID,
bool isRowSelected,
juce::Component* existingComponentToUpdate)
{
if (columnID == 2)
{
if (existingComponentToUpdate == nullptr)
{
juce::TextButton* btn = new juce::TextButton{ "play" };
btn->addListener(this);
existingComponentToUpdate = btn;
}
}
return existingComponentToUpdate;
}


Implement paintRowBackground and paintCell


Thursday 6 August 2020

We're going to build on our minimal implementation of that data model, and go ahead and actually write the code to draw the cells.

We've got two functions that we need to deal with here - paintRowBackground and paintCell.

1. In PlaylistComponent.cpp we can add this to the paintRowBackground function:
void PlaylistComponent::paintRowBackground(juce::Graphics& g,
int rowNumber,
int width,
int height,
bool rowIsSelected)
{
if (rowIsSelected)
{
g.fillAll(juce::Colours::orange);
}
else
{
g.fillAll(juce::Colours::darkgrey);
}
}
We have given it a variable g and we are changing the colour if a row is selected.

2. The next step, now we have drawn the background, is to draw the cell itself. Here is the function code:
void PlaylistComponent::paintCell(juce::Graphics& g,
int rowNumber,
int columnId,
int width,
int height,
bool rowIsSelected)
{
g.drawText (trackTitles[rowNumber],
2,
0,
width - 4,
height,
juce::Justification::centredLeft,
true);
}
Again we added a variable g in the function arguments.

3. When you compile and build you can see the track names in two columns. We'll get rid of the second column because we don't need it. So we can delete
tableComponent.getHeader().addColumn("Artist", 2, 400);
and we can also add a few more tracks too:
trackTitles.push_back("Track 3");
trackTitles.push_back("Track 4");
trackTitles.push_back("Track 5");
trackTitles.push_back("Track 6");

When you build and compile you will see the track names appear in the box.

Add a vector to store a list of files


Thursday 6 August 2020

We're going to build on the header we just added to our table by adding some actual data.

1. We're going to need somewhere to store that data. As a simple example we're going to have a vector containing strings.

Note! A vector of strings can only store a single string for each item in the playlist, but an item in the playlist has more than one piece of information associated with it. It has at least a song title and a filename. Perhaps a new class is needed? However this page deals with a vector.

In PlaylistComponent.h add at the top:
#include 
#include

2. In private: we declare the vector so we need to add:
std::vector trackTitles;

3. Go to PlaylistComponent.cpp and at the top of PlaylistComponent::PlaylistComponent() add:
    trackTitles.push_back("Track 1");
trackTitles.push_back("Track 2");

4. The data is now in the vector but how do we get the data onto the table? This is where it gets a little bit tricky. We need to inherit from TableListBoxModel. In PlaylistComponent.h add the inheritance as follows:
class PlaylistComponent  : public juce::Component, 
public juce::TableListBoxModel

5. Still in the header file, in public: add
    int getNumRows() override;

void paintRowBackground(juce::Graphics&,
int rowNumber,
int width,
int height,
bool rowIsSelected)
override;

void paintCell(juce::Graphics&,
int rowNumber,
int columnId,
int width,
int height,
bool rowIsSelected)
override;

6. We need to add the info we added in [5] above and put them in our .cpp file, so that we can implement them as functions. So go to PlaylistComponent.cpp and add:
int PlaylistComponent::getNumRows()
{
return trackTitles.size();
}

void PlaylistComponent::paintRowBackground(juce::Graphics&,
int rowNumber,
int width,
int height,
bool rowIsSelected)
{

}

void PlaylistComponent::paintCell(juce::Graphics&,
int rowNumber,
int columnId,
int width,
int height,
bool rowIsSelected)
{

}
These last two do not need to return anything so this is the minimum implementation of these functions needed so far.

7. We now need to add the data model to our component. Go back up in PC.cpp to the main constructor and under the tableComponent.getHeader we added add:
tableComponent.setModel(this);

We can now build and compile. Nothing has changed yet with the GUI but the TableListBoxModel has been implemented to the minimum amount.


Add a TableListBox


Thursday 6 August 2020

Here we are going to talk about the TableListBox component. This is a built-in class on the JUCE library which allows us to display a table.

1. In PlaylistComponent.h in private: add
juce::TableListBox tableComponent;

2. In PlaylistComponent.cpp in PlaylistComponent::PlaylistComponent() add:
addAndMakeVisible(tableComponent);

3. In resized() add:
tableComponent.setBounds(0, 0, getWidth(), getHeight());

When you compile and build now you can see a grey bar which is where the table header will go. So how can we specify the header?

4. We can configure the columns by going to PlaylistComponent::PlaylistComponent() in PlaylistComponent.cpp. Add the following code before the addAndMakeVisible:
tableComponent.getHeader().addColumn("Track title", 1, 400);

The 1 is the column ID (in Juce 6 this cannot be 0, so start at 1) and the 400 is the width in pixels.

5. We can add a new column, for example:
tableComponent.getHeader().addColumn("Artist", 2, 400);

On a build and compile you can now see the table starting to take shape.

Note: TableListBox inherits from multiple classes. The term for a class that can behave as multiple classes is polymorphism.


Create a PlaylistComponent in the Projucer project


Thursday 6 August 2020

1. Go to Projucer and add a new Component Class (split between a .cpp and a .h file). Then don't forget File > Save Project.

2. We want one playlist - not one for each deck - which we are going to add on the MainComponent. So in MainComponent.h add
#include "PlaylistComponent.h"

3. In private add the following:
PlaylistComponent playlistComponent;

4. In MainComponent.cpp go to the Main constructor and add
addAndMakeVisible(playlistComponent);

5. In resized() change the code to the following:
    deckGUI1.setBounds(0, 0, getWidth() / 2, getHeight() / 2);
deckGUI2.setBounds(getWidth() / 2, 0, getWidth() / 2, getHeight() / 2);
playlistComponent.setBounds(0, getHeight() / 2, getWidth(), getHeight() / 2);

On doing a compile and build this should now redraw the GUI.

alt