Unofficial Guide To Expanding Your Numworks/Adding your own application to the calculator
Alright, lets dive right in and add an application to the Home screen. All applications are added at compile time - there is currently no way to "install" an application on a running calculator like you would an app on your phone. To do this you are going to cheat a bit and clone then customize the calculator application. This gives us an application that we know is working, actually does something that we can play with, and then will let us change it to understand how the SDK works.
What's in a name?
editStart off with the easy part and just copy the Calculation application directory, yep the whole thing, into the same apps directory and rename your copy of the directory "foobar".
Every application lives in its own namespace. This allow each application to use a common naming convention for each of the classes that make up the application and not have collisions with the already existing application. The code you copied into the Foo folder uses the Calculation namespace, but that just won't do because the Calculation application is already using that namespace. So, next you need to give all the classes, namespaces, and other objects in Foo new names so they don't conflict with the Calculation application. It's good practice to use the actual application name as the base for all your names, so replace Calculation with Foo in our code. You'll need to do a search and replace in all .h and .cpp files plus the Makefile - within the Foo directory only - for three replacements to catch all the different cases. Make sure that you use case sensitive search and replace.
- "Calculation" -> "Foobar"
- "CALCULATION" -> "FOOBAR"
- "calculation" -> "foobar"
Save everything off and you now given your new app a name.
Quoi ? Vous ne parlez pas français ?
editEpsilon is setup to deal with multiple languages... in fact you have no choice but to deal with multiple languages. If you take a look in foobar/app.cpp you'll see some lines that look like this:
I18n::Message App::Descriptor::name() {
return I18n::Message::CalculApp;
}
I18n::Message App::Descriptor::upperName() {
return I18n::Message::CalculAppCapital;
}
I18n is the commonly accepted abbreviation for Internationalization because no one wants to type Internationalization over and over in their code. These two functions return the human readable version of your name in eithier Title Case or all caps, and they do it by looking up a message in an array defined in apps/i18n.h
and apps/i18n.cpp
. Open up the header file and you'll find an enum called Message that defines the magic symbols used by the compiler to identify every string message that we want to put on the screen. Scroll down in the file till you find this code and you'll see the magic symbols used in the name() and upperName() functions above.
Confidence,
/* Applications */
Apps,
AppsCapital,
/* Calculation */
CalculApp,
CalculAppCapital,
/* Fonction */
FunctionApp,
No where in this file will you find anything about our FooBar app, which is a real shame, so time to fix it. The absolute order and value of these symbols doesn't matter a lick except for one case we'll cover shortly. You define a pair of symbols for your application anywhere, but to keep things tidy we'll insert them right after the two Calcul symbols.
Confidence,
/* Applications */
Apps,
AppsCapital,
/* Calculation */
CalculApp,
CalculAppCapital,
/* FooBar */
FooBarApp,
FooBarAppCapital,
/* Fonction */
FunctionApp,
You also need to go back to your app.cpp and change the names there as well.
I18n::Message App::Descriptor::name() {
return I18n::Message::FooBarApp;
}
I18n::Message App::Descriptor::upperName() {
return I18n::Message::FooBarAppCapital;
}
Symbols now exist for your application's name and your Name functions will return the correct symbols, but nothing in the code gives those symbols any meaning and certainly not in multiple languages. To do that you need to edit i18n.cpp. Open it up and find these lines:
/* Applications */
{"Applications", "Applications", "Aplicaciones", "Anwendungen", "Aplicações"},
{"APPLICATIONS", "APPLICATIONS", "APLICACIONES", "ANWENDUNGEN", "APLICACOES"},
/* 1. Calculation */
{"Calculation", "Calculs", "Calculo", "Berechnung", "Calculo"},
{"CALCULATION", "CALCULS", "CALCULO", "BERECHNUNG", "CALCULO"},
/* 2. Function */
{"Functions", "Fonctions", "Funcion", "Funktionen", "Funcao"},
This structure should look very familiar as it mirrors the structure you saw in the i18n.h header file. Each of these lines matches up with one, and only one, of the symbols defined in i18n.cpp, but instead of a single string, this structure has an array of strings each corresponding to one language that Epsilon has been translated into. In order they are English, French, Spanish, German, and Portuguese. So, whenever you define a string that will be used for display on the string or for input from the keyboard, you'll need to define a new symbol, just like we did above, then provide the five translations for that string.
Now you are going to add the strings for your two versions of FooBar title, and this is the one place where the order of the symbols in the header file matters because you must put your strings in the same order in this structure as you used for the symbols in the header file. Since we put our symbols between Calculation and Function we need to add our strings between those two as well.
/* Applications */
{"Applications", "Applications", "Aplicaciones", "Anwendungen", "Aplicações"},
{"APPLICATIONS", "APPLICATIONS", "APLICACIONES", "ANWENDUNGEN", "APLICACOES"},
/* 1. Calculation */
{"Calculation", "Calculs", "Calculo", "Berechnung", "Calculo"},
{"CALCULATION", "CALCULS", "CALCULO", "BERECHNUNG", "CALCULO"},
/* 1a.FooBar */
{"FooBar", "FooBar", "FooBar", "FooBar", "FooBar"},
{"FOOBAR", "FOOBAR", "FOOBAR", "FOOBAR", "FOOBAR"},
/* 2. Function */
{"Functions", "Fonctions", "Funcion", "Funktionen", "Funcao"},
Since FooBar is such a world traveler, I've not had to translate, but Google Translate is your friend here and should be close enough. If Google translate gives you something really strange, then you can use your native language for that entry. If Google translate is wrong, someone who knows better than you will come along and correct the code in the future.
One important note here is that this means you cannot count on having any of your strings be a fixed length, so you'll need to use the string measurement functions and do some math to figure out your on screen layouts.
You also need to add two new slots to the messages array to hold your two new messages, so go to the declaration of the messages array find the lines below and change 240 to 242.
const char * messages[242][5] {
{"Warning", "Attention", "Cuidado", "Achtung", "Atencao"},
{"Confirm", "Valider", "Confirmar", "Bestatigen", "Confirmar"},
Save all this off, and FooBar is on its way to being welcomed around the world.
Selfie!
editEach application also has an icon that displays on the Home screen. The code for this icon is created at compile time from a .PNG file in your applications folder. If you go look in apps/foobar you'll find a calculation_icon.png. You'll need to rename this icon to foobar_icon.png because 1) it just makes more sense 2) during our search and replace all the references to calculation_icon.png were changed to foobar_icon.png. You may also see calculation_icon.h and calculation_icon.cpp - these are actually generated from the .PNG file at compile time, so just delete them to clean things up.
You can edit the .PNG in any image editing software to create an icon you like. When you are happy with the icon, save it off as a 32-bit RGBA PNG file. The inliner helper application can't deal with indexed or any other PNG scheme. There is also no harm in using the same icon for this experiment if you're not feeling artistic today.
Hooking it all together
editTime to let the rest of the OS know about your new application. If you've read the chapter on power on to application launch then you know the AppsContainer keeps track of all the applications on the calculator. First thing to do is tell the AppsContainer about your new application by editing epsilon/apps_container.h
and adding the new header. This is also where you start seeing how nice it is to have every application sharing the same naming conventions and structure but in a separate namespace.
#ifndef APPS_CONTAINER_H
#define APPS_CONTAINER_H
#include "home/app.h"
#include "graph/app.h"
#include "probability/app.h"
#include "calculation/app.h"
#include "foobar/app.h"
#include "regression/app.h"
#include "sequence/app.h"
#include "settings/app.h"
Next you need to add a parameter to the AppsContainer class to hold the snapshot for FooBar. These definitions come at the end of the class declaration in the same header file.
HardwareTest::App::Snapshot m_hardwareTestSnapshot;
OnBoarding::App::Snapshot m_onBoardingSnapshot;
Home::App::Snapshot m_homeSnapshot;
Calculation::App::Snapshot m_calculationSnapshot;
FooBar::App::Snapshot m_foobarSnapshot;
Graph::App::Snapshot m_graphSnapshot;
Sequence::App::Snapshot m_sequenceSnapshot;
Settings::App::Snapshot m_settingsSnapshot;
One last change and we are done with the header. The AppsContainer also maintains the count of Total Applications and the count of Common Applications. Common applications are just the ones that show up on the Home screen with icons. There are two other applications lurking in the background: the Home application itself and the OnBoarding application (don't worry about this for now.). Find the definition for k_numberOfCommonApps, which should be 9, and increment it by one for your new application. The next line in the header file is the definition for k_totalNumberOfApps which just adds 2 to the number of common apps - nothing to change on this one.
void resetShiftAlphaStatus();
static constexpr int k_numberOfCommonApps = 10; //Change this from 9 to 10
static constexpr int k_totalNumberOfApps = 2+k_numberOfCommonApps;
AppsWindow m_window;
Open up epsilon/apps_container.cpp and one of the first functions will be the constructor for the AppsContainer which also creates all the individual snapshots for each application. Go ahead and find the m_calculationSnapshot initializer and add your foobarSnapshot() initializer right after it.
AppsContainer::AppsContainer() :
Container(),
m_window(),
m_emptyBatteryWindow(),
m_globalContext(),
m_variableBoxController(&m_globalContext),
m_examPopUpController(),
m_updateController(),
m_ledTimer(LedTimer()),
m_batteryTimer(BatteryTimer(this)),
m_USBTimer(USBTimer(this)),
m_suspendTimer(SuspendTimer(this)),
m_backlightDimmingTimer(),
m_hardwareTestSnapshot(),
m_onBoardingSnapshot(),
m_homeSnapshot(),
m_calculationSnapshot(),
m_foobarSnapshot()<
m_graphSnapshot(),
m_sequenceSnapshot(),
m_settingsSnapshot(),
m_statisticsSnapshot(),
m_probabilitySnapshot(),
m_regressionSnapshot()
{
m_emptyBatteryWindow.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height));
Poincare::Expression::setCircuitBreaker(AppsContainer::poincareCircuitBreaker);
}
Next you need to add that snapshot to the list of applications in appSnapshotAtIndex()
. As you can probably guess, this is the function called whenever one application needs to find another application or when one application asks the OS to switch to another application. You can add your application anywhere in this list except as the first element - that position is reserved for the Home application. The order in this list will also control the order that the icons will appear on the Home screen. For consistency, put your snapshot right after the m_calculationSnapshot
.
App::Snapshot * AppsContainer::appSnapshotAtIndex(int index) {
if (index < 0) {
return nullptr;
}
App::Snapshot * snapshots[] = {
&m_homeSnapshot,
&m_calculationSnapshot,
&m_foobarSnapshot,
&m_graphSnapshot,
&m_sequenceSnapshot,
&m_settingsSnapshot,
&m_statisticsSnapshot,
&m_probabilitySnapshot,
&m_regressionSnapshot,
&m_codeSnapshot
};
assert(sizeof(snapshots)/sizeof(snapshots[0]) == k_numberOfCommonApps);
assert(index >= 0 && index < k_numberOfCommonApps);
return snapshots[index];
}
Lastly you need to let the Home application know to make room for an additional application as currently having more than two rows of icons doesn't work out well in the Home application. Open up apps/home/controller.h
and increment k_numberOfColumns by one. You'll need to increment this for every even numbered common application you add; this is common application 10 so we change from 4 to 5, you can add one more for 11 without incrementing it, but for 12 you'll need to increment this again from 5 to 6.
static constexpr KDCoordinate k_indicatorThickness = 28;
static constexpr KDCoordinate k_indicatorMargin = 116;
static constexpr int k_numberOfColumns = 5;
static constexpr int k_numberOfApps = 10;
static constexpr int k_maxNumberOfCells = 16;
Building
editLast step is to add your new application to the build process by editing apps/Makefile
to include apps/foobar/Makefile
right after the calculation Makefile.
include apps/calculation/Makefile
include apps/foobar/Makefile
include apps/graph/Makefile
include apps/home/Makefile
include apps/hardware_test/Makefile
Save everything off, make clean, make, and load the flash or simulator. When you get to the home screen, you'll see your new icon. Select it and you'll see the familiar functionality of the Calculation application. Moving forward we can use this as a basis to explore and see what happens as we change the FooBar code.