The Red Penguin

The Otford Solar System


Wednesday 7 October 2020

Otford Solar System

A typically British tourist attraction - a scale model of the solar system, created to celebrate the new millennium, showing the places of all of the planets on January 1, 2000. Except it doesn't actually show the planets, just concrete plinths (mostly). And it also has Pluto!

Sounds like exactly the kind of thing I need to go and visit, and it's only 35 minutes drive from darkest Essex so I headed on over to Otford in Kent today - a very autumnal afternoon - to see if I could find them all or whether they'd been stolen or vandalised over the last 20 years.

Otford Solar System

Otford Solar System

The sun and most of the inner planets are at the far end of Otford Recreational Ground - a couple of minutes' walk from the car park. So it was easy to find the Sun, Mercury, Venus and Earth and read up a little bit about the scale they used.

Otford Solar System

Otford Solar System

Otford Solar System

Otford Solar System

Otford Solar System

Otford Solar System

I had a lot of trouble finding Mars. It is the only planet not to appear on a plinth, as it sits between two football pitches marked out on the rec ground. I wandered around for a while looking for it, walking up to and around Otford United, who play in the 11th tier of English football in the Kent County Premier League. I'd have said it has the most picturesque views of any ground at that level, although I haven't seen the rest of the Kent teams ...

Otford United

I finally found Mars and then wandered out to Jupiter, which lies on the side of the rec ground.

Otford Solar System

By now I started to use the Ordnance Survey map on my phone - not the app, but the website here. All of the plinths are marked on the map as memorials (Memls for everything from the Sun to Mars, and Meml for all of the others). Some of them are also marked on Google maps, although I assume that the OS map is the most accurate.

To find Saturn you need to walk out of the car park, turn left and then left again after the roundabout into Leonard Avenue. Saturn is sitting in front of a doctor's surgery at the end of the road.

Otford Solar System

I decided to have some lunch before venturing out to the last three planets (or the last two plus Pluto, depending on how strongly you feel about that). I also wandered over to the duck pond, which for some reason has been a Grade II listed building since 1975.

Otford duck pond

Otford duck pond

Unfortunately I spent so long waiting for my lunch, I realised I wouldn't be able to walk down to the other three planets and back before my time in the car park ran out. So I decided to cheat a little bit and I drove down to Neptune, which is just over 1km away in Telston Lane, about 20 metres from Pilgrims Way.

Otford Solar System

Uranus is about 400m back towards the village centre, next to a bus stop by Frog Farm. Luckily this is almost exactly at the point where a footpath starts, which eventually takes you to Pluto 1.25km later.

Otford Solar System

The track was a little bit muddy in places and it was signposted a couple of times too, although you should make sure you have a map, otherwise you might not find the right turn which takes you on the final stretch to the last plinth.

Otford Solar System

Otford Solar System

Otford Solar System

Otford Solar System

Otford Solar System

The main information board suggests it takes 2 hours to visit all the planets. I took about 1 hour and 15 minutes - I missed a walking round trip of about 20 minutes by driving down the road to Neptune, but I also spent 5-10 minutes looking for Mars so I think the whole thing could be done in one and a half hours.

The Otford solar system claims to be the largest model of its type in the world, because it's added an additional plinth for Proxima Centauri - which is sitting in the Griffin Observatory in Los Angeles. So that's one for the list of places to visit one day ...



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.