diff --git a/Inc/Buzzer.h b/Inc/Buzzer.h new file mode 100644 index 0000000..1a20f4f --- /dev/null +++ b/Inc/Buzzer.h @@ -0,0 +1,31 @@ +/* + * Buzzer.h + * + * Created: 13/11/2014 22:56:34 + * Author: David + */ + + +#ifndef BUZZER_H_ +#define BUZZER_H_ + +namespace Buzzer +{ + void Init(); + + void Beep(uint32_t frequency, uint32_t ms, uint32_t volume); + + void Tick(); + + bool Noisy(); + + void SetBacklight(uint32_t brightness); + + const uint32_t MaxVolume = 5; + const uint32_t DefaultVolume = 3; + const uint32_t MaxBrightness = 100; + const uint32_t MinBrightness = 5; // avoid making it so dark that it cannot be seen + const uint32_t DefaultBrightness = 100; // default = maximum because lower levels make the backlight inverter buzz +} + +#endif /* BUZZER_H_ */ diff --git a/Inc/Display.h b/Inc/Display.h new file mode 100644 index 0000000..db23354 --- /dev/null +++ b/Inc/Display.h @@ -0,0 +1,556 @@ +/* + * Display.h + * + * Created: 04/11/2014 09:43:43 + * Author: David + */ + + +#ifndef DISPLAY_H_ +#define DISPLAY_H_ + +#include "ecv.h" +#include "UTFT.h" + +#ifndef UNUSED +# define UNUSED(_x) (void)(_x) +#endif + +// Fonts are held as arrays of 8-bit data in flash. +typedef const uint8_t * array LcdFont; + +// An icon is stored an array of uint16_t data normally held in flash memory. The first value is the width in pixels, the second is the height in pixels. +// After that comes the icon data, 16 bits per pixel, one row at a time. +typedef const uint8_t * array Icon; + +// Unicode strings for special characters in our font +#define DECIMAL_POINT "\xC2\xB7" // Unicode middle-dot, code point B7 +#define DEGREE_SYMBOL "\xC2\xB0" // Unicode degree-symbol, code point B0 +#define THIN_SPACE "\xC2\x80" // Unicode control character, code point 0x80, we use it as thin space + +const uint8_t buttonGradStep = 12; +const PixelNumber AutoPlace = 0xFFFF; + +typedef uint8_t event_t; +const event_t nullEvent = 0; + +enum class TextAlignment : uint8_t { Left, Centre, Right }; + +class ButtonBase; + +// Small by-value class to identify what button has been pressed +class ButtonPress +{ + ButtonBase * null button; + unsigned int index; + +public: + ButtonPress(); + ButtonPress(ButtonBase *b, unsigned int pi); + void Clear(); + + bool IsValid() const { return button != nullptr; } + ButtonBase * GetButton() const { return button; } + unsigned int GetIndex() const { return index; } + + event_t GetEvent() const; + int GetIParam() const; + const char* array GetSParam() const; + bool operator==(const ButtonPress& other) const; + + bool operator!=(const ButtonPress& other) const { return !operator==(other); } +}; + +// Base class for a displayable field +class DisplayField +{ +protected: + PixelNumber y, x; // Coordinates of top left pixel, counting from the top left corner + PixelNumber width; // number of pixels occupied in each direction + Colour fcolour, bcolour; // foreground and background colours + uint16_t changed : 1, + visible : 1, + underlined : 1, // really belongs in class FieldWithText, but stored here to save space + border : 1, // really belongs in class FieldWithText, but stored here to save space + textRows : 2; // really belongs in class FieldWithText, but stored here to save space + + static LcdFont defaultFont; + static Colour defaultFcolour, defaultBcolour; + static Colour defaultButtonBorderColour, defaultGradColour, defaultPressedBackColour, defaultPressedGradColour; + static Palette defaultIconPalette; + +protected: + DisplayField(PixelNumber py, PixelNumber px, PixelNumber pw); + + void SetTextRows(const char * array t); + + virtual PixelNumber GetHeight() const = 0; + +public: + DisplayField * null next; // link to next field in list + + virtual bool IsButton() const { return false; } + bool IsVisible() const { return visible; } + void Show(bool v); + virtual void Refresh(bool full, PixelNumber xOffset, PixelNumber yOffset) = 0; + void SetColours(Colour pf, Colour pb); + void SetChanged() { changed = true; } + PixelNumber GetMinX() const { return x; } + PixelNumber GetMaxX() const { return x + width - 1; } + PixelNumber GetMinY() const { return y; } + PixelNumber GetMaxY() const { return y + GetHeight() - 1; } + + virtual event_t GetEvent() const { return nullEvent; } + + static void SetDefaultColours(Colour pf, Colour pb) { defaultFcolour = pf; defaultBcolour = pb; } + static void SetDefaultColours(Colour pf, Colour pb, Colour pbb, Colour pg, Colour pbp, Colour pgp, Palette pal); + static void SetDefaultFont(LcdFont pf) { defaultFont = pf; } + static ButtonPress FindEvent(PixelNumber x, PixelNumber y, DisplayField * null p); + + // Icon management + static PixelNumber GetIconWidth(Icon ic) { return ic[0]; } + static PixelNumber GetIconHeight(Icon ic) { return ic[1]; } + static const uint8_t * array GetIconData(Icon ic) { return ic + 2; } + + static PixelNumber GetTextWidth(const char* array s, PixelNumber maxWidth); // find out how much width we need to print this text +}; + +class PopupWindow; + +class Window +{ +protected: + DisplayField * null root; + PopupWindow * null next; + Colour backgroundColour; + +public: + Window(Colour pb); + virtual PixelNumber Xpos() const { return 0; } + virtual PixelNumber Ypos() const { return 0; } + void AddField(DisplayField *p); + ButtonPress FindEvent(PixelNumber x, PixelNumber y); + ButtonPress FindEventOutsidePopup(PixelNumber x, PixelNumber y); + DisplayField * null GetRoot() const { return root; } + virtual void Refresh(bool full) = 0; + void Redraw(DisplayField *f); + void Show(DisplayField * null f, bool v); + void Press(ButtonPress bp, bool v); + void SetPopup(PopupWindow * p, PixelNumber px = 0, PixelNumber py = 0, bool redraw = true); + PopupWindow * null GetPopup() const { return next; } + void ClearPopup(bool redraw = true, PopupWindow *whichOne = nullptr); + bool ObscuredByPopup(const DisplayField *p) const; + bool Visible(const DisplayField *p) const; + virtual bool Contains(PixelNumber xmin, PixelNumber ymin, PixelNumber xmax, PixelNumber ymax) const = 0; +}; + +class MainWindow : public Window +{ + PixelNumber staticLeftMargin; + +public: + MainWindow(); + void Init(Colour pb); + void Refresh(bool full) override; + void SetRoot(DisplayField * null r) { root = r; } + bool Contains(PixelNumber xmin, PixelNumber ymin, PixelNumber xmax, PixelNumber ymax) const override; + void ClearAllPopups(); + void SetLeftMargin(PixelNumber m) { staticLeftMargin = m; } +}; + +class PopupWindow : public Window +{ +private: + PixelNumber height, width, xPos, yPos; + Colour borderColour; + +public: + PopupWindow(PixelNumber ph, PixelNumber pw, Colour pb, Colour pBorder); + + PixelNumber GetHeight() const { return height; } + PixelNumber GetWidth() const { return width; } + PixelNumber Xpos() const override { return xPos; } + PixelNumber Ypos() const override { return yPos; } + void Refresh(bool full) override; + void SetPos(PixelNumber px, PixelNumber py) { xPos = px; yPos = py; } + bool Contains(PixelNumber xmin, PixelNumber ymin, PixelNumber xmax, PixelNumber ymax) const override; +}; + +// Base class for fields displaying text +class FieldWithText : public DisplayField +{ + LcdFont font; + TextAlignment align; + +protected: + PixelNumber GetHeight() const override; + + virtual void PrintText() const = 0; + + FieldWithText(PixelNumber py, PixelNumber px, PixelNumber pw, TextAlignment pa, bool withBorder, bool isUnderlined = false) + : DisplayField(py, px, pw), font(DisplayField::defaultFont), align(pa) + { + underlined = isUnderlined; + border = withBorder; + textRows = 1; + } + +public: + void Refresh(bool full, PixelNumber xOffset, PixelNumber yOffset) override final; +}; + +// Class to display a fixed label and some variable text +class TextField : public FieldWithText +{ + const char* array null label; + const char* array null text; + +protected: + void PrintText() const override; + +public: + TextField(PixelNumber py, PixelNumber px, PixelNumber pw, TextAlignment pa, + const char * array null pl, const char* array null pt = nullptr, bool withBorder = false) + : FieldWithText(py, px, pw, pa, withBorder), label(pl), text(pt) + { + } + + void SetValue(const char* array s) + { + text = s; + changed = true; + } + + void SetLabel(const char* array s) + { + label = s; + changed = true; + } +}; + +// Class to display an optional label, a floating point value, and an optional units string +class FloatField : public FieldWithText +{ + const char* array null label; + const char* array null units; + float val; + uint8_t numDecimals; + +protected: + void PrintText() const override; + +public: + FloatField(PixelNumber py, PixelNumber px, PixelNumber pw, TextAlignment pa, uint8_t pd, + const char * array pl = nullptr, const char * array null pu = nullptr, bool withBorder = false) + : FieldWithText(py, px, pw, pa, withBorder), label(pl), units(pu), val(0.0), numDecimals(pd) + { + } + + void SetValue(float v) + { + val = v; + changed = true; + } +}; + +// Class to display an optional label, an integer value, and an optional units string +class IntegerField : public FieldWithText +{ + const char* array null label; + const char* array null units; + int val; + +protected: + void PrintText() const override; + +public: + IntegerField(PixelNumber py, PixelNumber px, PixelNumber pw, TextAlignment pa, + const char *pl = nullptr, const char *pu = nullptr, bool withBorder = false) + : FieldWithText(py, px, pw, pa, withBorder), label(pl), units(pu), val(0) + { + } + + void SetValue(int v) + { + val = v; + changed = true; + } +}; + +// Class to display a text string only +class StaticTextField : public FieldWithText +{ + const char * array null text; + +protected: + void PrintText() const override; + +public: + StaticTextField(PixelNumber py, PixelNumber px, PixelNumber pw, TextAlignment pa, const char * array null pt, bool isUnderlined = false) + : FieldWithText(py, px, pw, pa, false, isUnderlined), text(pt) + { + SetTextRows(pt); + } + + // Change the value + void SetValue(const char* array null pt) + { + text = pt; + SetTextRows(pt); + changed = true; + } +}; + +class ButtonBase : public DisplayField +{ +protected: + Colour borderColour, gradColour, pressedBackColour, pressedGradColour; + event_t evt; // event number that is triggered by touching this field + bool pressed; // putting this here instead of in SingleButton saves 4 byes per button + + ButtonBase(PixelNumber py, PixelNumber px, PixelNumber pw); + + void DrawOutline(PixelNumber xOffset, PixelNumber yOffset, bool isPressed) const; + + static PixelNumber textMargin; + static PixelNumber iconMargin; + +public: + event_t GetEvent() const override { return evt; } + virtual const char* null GetSParam(unsigned int index) const { UNUSED(index); return nullptr; } + virtual int GetIParam(unsigned int index) const { UNUSED(index); return 0; } + virtual void Press(bool p, int index) { UNUSED(p); UNUSED(index); } +}; + +class SingleButton : public ButtonBase +{ + union + { + const char* null sParam; + int iParam; + //float fParam; + } param; + +protected: + SingleButton(PixelNumber py, PixelNumber px, PixelNumber pw); + + void DrawOutline(PixelNumber xOffset, PixelNumber yOffset) const; + +public: + bool IsButton() const override final { return true; } + + void SetEvent(event_t e, const char* null sp ) { evt = e; param.sParam = sp; } + void SetEvent(event_t e, int ip ) { evt = e; param.iParam = ip; } + //void SetEvent(event_t e, float fp ) { evt = e; param.fParam = fp; } + + const char* null GetSParam(unsigned int index) const override { UNUSED(index); return param.sParam; } + int GetIParam(unsigned int index) const override { UNUSED(index); return param.iParam; } + //float GetFParam() const { return param.fParam; } + + void Press(bool p, int index) override + { + UNUSED(p); UNUSED(index); + if (p != pressed) + { + pressed = p; + changed = true; + } + } + + static void SetTextMargin(PixelNumber p) { textMargin = p; } + static void SetIconMargin(PixelNumber p) { iconMargin = p; } +}; + +class ButtonWithText : public SingleButton +{ + static LcdFont font; + +protected: + PixelNumber GetHeight() const override; + + virtual size_t PrintText(size_t offset) const = 0; + +public: + ButtonWithText(PixelNumber py, PixelNumber px, PixelNumber pw) + : SingleButton(py, px, pw) {} + + void Refresh(bool full, PixelNumber xOffset, PixelNumber yOffset) override final; + + static void SetFont(LcdFont f) { font = f; } +}; + +// Character button. The character on the button is the same as the integer event parameter. +class CharButton : public ButtonWithText +{ +protected: + size_t PrintText(size_t offset) const override; + +public: + CharButton(PixelNumber py, PixelNumber px, PixelNumber pw, char pc, event_t e); +}; + +#if 0 // not used yet +class ButtonRow : public ButtonBase +{ +protected: + unsigned int numButtons; + int whichPressed; + PixelNumber step; + +public: + ButtonRow(PixelNumber py, PixelNumber px, PixelNumber pw, PixelNumber ps, unsigned int nb, event_t e); +}; + +class ButtonRowWithText : public ButtonRow +{ + LcdFont font; + +protected: + virtual void PrintText(unsigned int n) const { } + +public: + ButtonRowWithText(PixelNumber py, PixelNumber px, PixelNumber pw, PixelNumber ps, unsigned int nb, event_t e); + + void Refresh(bool full, PixelNumber xOffset, PixelNumber yOffset) override final; +}; + +class CharButtonRow : public ButtonRowWithText +{ + const char * array text; + +protected: + void PrintText(unsigned int n) const override; + +public: + CharButtonRow(PixelNumber py, PixelNumber px, PixelNumber pw, PixelNumber ps, const char * array s, event_t e); +}; +#endif + +class TextButton : public ButtonWithText +{ + const char * array null text; + +protected: + size_t PrintText(size_t offset) const override; + +public: + TextButton(PixelNumber py, PixelNumber px, PixelNumber pw, const char * array null pt, event_t e, int param = 0); + TextButton(PixelNumber py, PixelNumber px, PixelNumber pw, const char * array null pt, event_t e, const char * array param); + + void SetText(const char* array null pt) + { + text = pt; + changed = true; + } +}; + +class IconButton : public SingleButton +{ + Icon icon; + +protected: + PixelNumber GetHeight() const override { return GetIconHeight(icon) + 2 * iconMargin + 2; } + +public: + IconButton(PixelNumber py, PixelNumber px, PixelNumber pw, Icon ic, event_t e, int param = 0); + IconButton(PixelNumber py, PixelNumber px, PixelNumber pw, Icon ic, event_t e, const char * array param); + + void Refresh(bool full, PixelNumber xOffset, PixelNumber yOffset) override final; +}; + +class IntegerButton : public ButtonWithText +{ + const char* array null label; + const char* array null units; + int val; + +protected: + size_t PrintText(size_t offset) const override; + +public: + IntegerButton(PixelNumber py, PixelNumber px, PixelNumber pw, const char * array pl = nullptr, const char * array pt = nullptr) + : ButtonWithText(py, px, pw), label(pl), units(pt), val(0) {} + + int GetValue() const { return val; } + + void SetValue(int pv) + { + val = pv; + changed = true; + } + + void Increment(int amount) + { + val += amount; + changed = true; + } +}; + +class FloatButton : public ButtonWithText +{ + const char * array null units; + float val; + uint8_t numDecimals; + +protected: + size_t PrintText(size_t offset) const override; + +public: + FloatButton(PixelNumber py, PixelNumber px, PixelNumber pw, uint8_t pd, const char * array pt = nullptr) + : ButtonWithText(py, px, pw), units(pt), val(0.0), numDecimals(pd) {} + + float GetValue() const { return val; } + + void SetValue(float pv) + { + val = pv; + changed = true; + } + + void Increment(int amount) + { + val += amount; + changed = true; + } +}; + +class ProgressBar : public DisplayField +{ + PixelNumber lastNumPixelsSet; + uint8_t height; + uint8_t percent; + +public: + ProgressBar(uint16_t py, uint16_t px, uint8_t ph, uint16_t pw) + : DisplayField(py, px, pw), lastNumPixelsSet(0), height(ph), percent(0) + { + } + + void Refresh(bool full, PixelNumber xOffset, PixelNumber yOffset) override; + + PixelNumber GetHeight() const override { return height; } + + void SetPercent(uint8_t pc) + { + percent = pc; + changed = true; + } +}; + +class StaticImageField: public DisplayField +{ + const uint16_t * array data; // compressed bitmap + PixelNumber height; + +public: + StaticImageField(PixelNumber py, PixelNumber px, PixelNumber ph, PixelNumber pw, const uint16_t * array imageData) + : DisplayField(py, px, pw), data(imageData), height(ph) + { + } + + void Refresh(bool full, PixelNumber xOffset, PixelNumber yOffset) override; + + PixelNumber GetHeight() const override { return height; } +}; + +#endif /* DISPLAY_H_ */ diff --git a/Inc/FileManager.h b/Inc/FileManager.h new file mode 100644 index 0000000..cbec66d --- /dev/null +++ b/Inc/FileManager.h @@ -0,0 +1,83 @@ +/* + * FileManager.h + * + * Created: 06/11/2015 10:52:38 + * Author: David + */ + +#ifndef FILEMANAGER_H_ +#define FILEMANAGER_H_ + +#include "Vector.h" +#include "RequestTimer.h" +#include "Events.h" + +namespace FileManager +{ + const size_t maxPathLength = 100; + typedef String Path; + + class FileSet + { + private: + Path requestedPath; + Path currentPath; + RequestTimer timer; + int which; + const Event fileEvent; + int scrollOffset; + bool IsInSubdir() const; + const bool isFilesList; // true for a file list, false for a macro list + uint8_t cardNumber; + + public: + FileSet(Event fe, const char * array rootDir, bool pIsFilesList); + void Display(); + void Reload(int whichList, const Path& dir, int errCode); + void FileListUpdated(); + void Scroll(int amount); + void SetIndex(int index) { which = index; } + int GetIndex() const { return which; } + void SetPath(const char * array pPath); + const char * array GetPath() { return currentPath.c_str(); } + void RequestParentDir() + pre(IsInSubdir()); + void RequestSubdir(const char * array dir); + void SetPending(); + void StopTimer() { timer.Stop(); } + bool ProcessTimer() { return timer.Process(); } + bool NextCard(); + bool SelectCard(unsigned int cardNum); + void FirmwareFeaturesChanged(); + + private: + void SetupRootPath(); + }; + + void BeginNewMessage(); + void EndReceivedMessage(bool displayingFileInfo); + void BeginReceivingFiles(); + void ReceiveFile(const char * array data); + void ReceiveDirectoryName(const char * array data); + void ReceiveErrorCode(int err); + + void DisplayFilesList(); + void DisplayMacrosList(); + void Scroll(int amount); + + void RequestFilesSubdir(const char * array dir); + void RequestMacrosSubdir(const char * array dir); + void RequestFilesParentDir(); + void RequestMacrosParentDir(); + const char * array GetFilesDir(); + const char * array GetMacrosDir(); + + void RefreshFilesList(); + bool ProcessTimers(); + bool NextCard(); + bool SelectCard(unsigned int cardNum); + void SetNumVolumes(size_t n); + void FirmwareFeaturesChanged(); +} + +#endif /* FILEMANAGER_H_ */ diff --git a/Inc/MessageLog.h b/Inc/MessageLog.h new file mode 100644 index 0000000..74848ae --- /dev/null +++ b/Inc/MessageLog.h @@ -0,0 +1,34 @@ +/* + * MessageLog.h + * + * Created: 15/11/2015 10:55:02 + * Author: David + */ + + +#ifndef MESSAGELOG_H_ +#define MESSAGELOG_H_ + +#include "Display.h" + +namespace MessageLog +{ + void Init(); + + // Update the messages on the message tab. If 'all' is true we do the times and the text, else we just do the times. + void UpdateMessages(bool all); + + // Add a message to the end of the list. It will be just off the visible part until we scroll it in. + void AppendMessage(const char* data); + + // If there is a new message, scroll it in + void DisplayNewMessage(); + + // This is called when we receive a new response from the host, which may or may not include a new message for the log + void BeginNewMessage(); + + // Find where we need to split a text string so that it will fit in a field + size_t FindSplitPoint(const char * array s, size_t maxChars, PixelNumber width); +} + +#endif /* MESSAGELOG_H_ */ \ No newline at end of file diff --git a/Inc/Misc.h b/Inc/Misc.h new file mode 100644 index 0000000..8331510 --- /dev/null +++ b/Inc/Misc.h @@ -0,0 +1,43 @@ +/* + * Misc.h + * + * Created: 14/11/2014 19:56:03 + * Author: David + */ + + +#ifndef MISC_H_ +#define MISC_H_ + +#include + +#undef min +#undef max + +#define ARRAY_SIZE(_x) (sizeof(_x)/sizeof(_x[0])) + +// Safe version of strncpy that ensures that the destination is always null-terminated on return +void safeStrncpy(char* array dst, const char* array src, size_t n) +pre(n != 0; _ecv_isNullTerminated(src); dst.upb >= n) +post(_ecv_isNullTerminated(dst)); + +// Return true if string a is the same as or starts with string b +bool stringStartsWith(const char* array a, const char* array b) +pre(_ecv_isNullTerminated(a); _ecv_isNullTerminated(b)); + +template T min(const T& a, const T& b) +{ + return (a < b) ? a : b; +} + +template T max(const T& a, const T& b) +{ + return (a > b) ? a : b; +} + +template T constrain(const T& v, const T& minVal, const T& maxVal) +{ + return (v < minVal) ? minVal : (v > maxVal) ? maxVal : v; +} + +#endif /* MISC_H_ */ diff --git a/Inc/PanelDue.h b/Inc/PanelDue.h new file mode 100644 index 0000000..8074b73 --- /dev/null +++ b/Inc/PanelDue.h @@ -0,0 +1,61 @@ +/* + * PanelDue.h + * + * Created: 06/12/2014 14:23:38 + * Author: David + */ + +#ifndef PANELDUE_H_ +#define PANELDUE_H_ + +#include "UTFT.h" +#include "Display.h" +#include "RequestTimer.h" +#include "PrinterStatus.h" +#include "UserInterface.h" + +// Functions called from the serial I/O module +extern void ProcessReceivedValue(const char id[], const char val[], int index); +extern void ProcessArrayLength(const char id[], int length); +extern void StartReceivedMessage(); +extern void EndReceivedMessage(); + +// Functions called from module UserInterface +extern bool PrintInProgress(); +extern PrinterStatus GetStatus(); +extern void DelayTouchLong(); +extern void ShortenTouchDelay(); +extern void TouchBeep(); +extern void ErrorBeep(); +extern void CalibrateTouch(); + +// Functions called from module UserInterface to manipulate non-volatile settings and associated hardware +extern void FactoryReset(); +extern void SaveSettings(); +extern bool IsSaveAndRestartNeeded(); +extern bool IsSaveNeeded(); +extern void MirrorDisplay(); +extern void InvertDisplay(); +extern void SetBaudRate(uint32_t rate); +extern void SetBrightness (int percent); +extern void SetVolume(uint32_t newVolume); +extern void SetColourScheme(uint32_t newColours); +extern void SetLanguage(uint32_t newLanguage); +extern uint32_t GetBaudRate(); +extern int GetBrightness(); +extern uint32_t GetVolume(); +extern FirmwareFeatures GetFirmwareFeatures(); +extern const char* array CondStripDrive(const char* array arg); +extern void Reconnect(); +extern void Delay(uint32_t milliSeconds); + +// Global data in PanelDue.cpp that is used elsewhere +extern UTFT lcd; +extern MainWindow mgr; + +class ColourScheme; +extern const ColourScheme *colours; + +const size_t MIN_AXES = 3; // the minimum number of axes we support + +#endif /* PANELDUE_H_ */ diff --git a/Inc/PrinterStatus.h b/Inc/PrinterStatus.h new file mode 100644 index 0000000..bed2d12 --- /dev/null +++ b/Inc/PrinterStatus.h @@ -0,0 +1,28 @@ +/* + * PrinterStatus.h + * + * Created on: 6 Jan 2017 + * Author: David + */ + +#ifndef SRC_PRINTERSTATUS_HPP_ +#define SRC_PRINTERSTATUS_HPP_ + +// Status that the printer may report to us. +// The array 'statusText' must be kept in sync with this! +enum class PrinterStatus +{ + connecting = 0, + idle = 1, + printing = 2, + stopped = 3, + configuring = 4, + paused = 5, + busy = 6, + pausing = 7, + resuming = 8, + flashing = 9, + toolChange = 10 +}; + +#endif /* SRC_PRINTERSTATUS_HPP_ */ diff --git a/Inc/RequestTimer.h b/Inc/RequestTimer.h new file mode 100644 index 0000000..e91ae7e --- /dev/null +++ b/Inc/RequestTimer.h @@ -0,0 +1,29 @@ +/* + * RequestTimer.h + * + * Created: 06/11/2015 14:22:05 + * Author: David + */ + + +#ifndef REQUESTTIMER_H_ +#define REQUESTTIMER_H_ + +class RequestTimer +{ + enum { stopped, running, ready } timerState; + uint32_t startTime; + uint32_t delayTime; + const char * array command; + const char * array null extra; + +public: + RequestTimer(uint32_t del, const char * array cmd, const char * array null ex = nullptr); + void SetCommand(const char * array cmd) { command = cmd; } + void SetArgument(const char * array null arg) { extra = arg; } + void SetPending() { timerState = ready; } + void Stop() { timerState = stopped; } + bool Process(); +}; + +#endif /* REQUESTTIMER_H_ */ diff --git a/Inc/Reset.h b/Inc/Reset.h new file mode 100644 index 0000000..b66e9ca --- /dev/null +++ b/Inc/Reset.h @@ -0,0 +1,20 @@ +/* + * Reset.h + * + * Created: 07/11/2015 11:46:58 + * Author: David + */ + + +#ifndef RESET_H_ +#define RESET_H_ + +#include "stm32f1xx_hal.h" + +// Restart the hardware +inline void Restart() +{ + NVIC_SystemReset(); +} + +#endif /* RESET_H_ */ diff --git a/Inc/SerialIo.h b/Inc/SerialIo.h new file mode 100644 index 0000000..c40ec1c --- /dev/null +++ b/Inc/SerialIo.h @@ -0,0 +1,22 @@ +/* + * SerialIo.hpp + * + * Created: 09/11/2014 09:20:46 + * Author: David + */ + + +#ifndef SERIALIO_H_ +#define SERIALIO_H_ + +namespace SerialIo +{ + void Init(uint32_t baudRate); + void SendChar(char c); + void SendString(const char * array s); + void SendFilename(const char * array dir, const char * array name); + void SendInt(int i); + void CheckInput(); +} + +#endif /* SERIALIO_H_ */ \ No newline at end of file diff --git a/Inc/Strings.h b/Inc/Strings.h new file mode 100644 index 0000000..97bf5d0 --- /dev/null +++ b/Inc/Strings.h @@ -0,0 +1,591 @@ +/* + * Strings.hpp + * + * Created on: 27 Feb 2017 + * Author: David + * + * The encoding used for this file must be UTF-8 to ensure that accented characters are displayed correctly. + */ + +#ifndef SRC_STRINGS_HPP_ +#define SRC_STRINGS_HPP_ + +#include "ecv.h" + +#define CSTRING const char * const array +#define Newline "\n" +#define DegreeSymbol "\u00B0" + +struct StringTable +{ + // Language name + CSTRING languageName; + + // Main page strings + CSTRING control; + CSTRING print; + CSTRING console; + CSTRING setup; + CSTRING current; + CSTRING active; + CSTRING standby; + CSTRING move; + CSTRING extrusion; + CSTRING macro; + CSTRING stop; + + // Print page + CSTRING extruderPercent; + CSTRING speed; + CSTRING fan; + CSTRING timeRemaining; + CSTRING file; + CSTRING filament; + CSTRING layer; + CSTRING notAvailable; + CSTRING pause; + CSTRING babystep; + CSTRING resume; + CSTRING cancel; + + // Setup page + CSTRING volume; + CSTRING calibrateTouch; + CSTRING mirrorDisplay; + CSTRING invertDisplay; + CSTRING theme; + CSTRING brightnessDown; + CSTRING brightnessUp; + CSTRING saveSettings; + CSTRING clearSettings; + CSTRING saveAndRestart; + + // Misc + CSTRING confirmFactoryReset; + CSTRING confirmRestart; + CSTRING confirmFileDelete; + CSTRING areYouSure; + CSTRING touchTheSpot; + CSTRING settingsNotSavedText; + CSTRING restartNeededText; + CSTRING moveHead; + CSTRING extrusionAmount; + CSTRING extrusionSpeed; + CSTRING extrude; + CSTRING retract; + CSTRING babyStepping; + CSTRING currentZoffset; + CSTRING message; + CSTRING messages; + CSTRING firmwareVersion; + + // File popup + CSTRING filesOnCard; + CSTRING macros; + CSTRING error; + CSTRING accessingSdCard; + CSTRING fileName; + CSTRING fileSize; + CSTRING layerHeight; + CSTRING objectHeight; + CSTRING filamentNeeded; + CSTRING generatedBy; + + // Printer status strings + CSTRING statusValues[11]; + + // Colour theme names + CSTRING colourSchemeNames[NumColourSchemes]; +}; + +const StringTable LanguageTables[4] = +{ + // English + { + // Language name + "English", + + // Main page strings + "Control", + "Print", + "Console", + "Setup", + "Current" THIN_SPACE DEGREE_SYMBOL "C", + "Active" THIN_SPACE DEGREE_SYMBOL "C", + "Standby" THIN_SPACE DEGREE_SYMBOL "C", + "Move", + "Extrusion", + "Macro", + "STOP", + + // Print page + "Extruder" THIN_SPACE "%", + "Speed ", // note space at end + "Fan ", // note space at end + "Time left: ", + "file ", // note space at end + ", filament ", // note space at end + ", layer ", // note space at end + "n/a", + "Pause", + "Baby step", + "Resume", + "Cancel", + + // Setup page + "Volume ", // note space at end + "Calibrate touch", + "Mirror display", + "Invert display", + "Theme", + "Brightness -", + "Brightness +", + "Save settings", + "Clear settings", + "Save & Restart", + + // Misc + "Confirm factory reset", + "Confirm restart", + "Confirm file delete", + "Are you sure?", + "Touch the spot", + "Some settings are not saved!", + "Touch Save & Restart to use new settings", + "Move head", + "Extrusion amount (mm)", + "Speed (mm/s)", + "Extrude", + "Retract", + "Baby stepping", + "Current Z offset: ", + "Message", + "Messages", + "Panel Due firmware version ", // note space at end + + // File popup + "Files on card ", // note the space on the end + "Macros", + "Error ", // note the space at the end + " accessing SD card", // note the space at the start + "Filename: ", + "Size: ", + "Layer height: ", + "Object height: ", + "Filament needed: ", + "Sliced by: ", + + // Printer status strings + { + "Connecting", + "Idle", + "Printing", + "Halted", + "Starting up", + "Paused", + "Busy", + "Pausing", + "Resuming", + "Firmware upload", + "Changing tool" + }, + + // Theme names + { + "Light", + "Dark" + } + }, + + // German + { + // Language name + "Deutsch", + + // Main page strings + "Steuerung", + "Druck", + "Konsole", + "Einrichtung", + "Aktuell" THIN_SPACE DEGREE_SYMBOL "C", + "Aktiv" THIN_SPACE DEGREE_SYMBOL "C", + "Standby" THIN_SPACE DEGREE_SYMBOL "C", + "Bewegung", + "Extrusion", + "Makro", + "NOT-AUS", + + // Print page + "Extruder" THIN_SPACE "%", + "Geschwindigkeit ", // note space at end + "Lüfter ", // note space at end + "Zeit übrig: ", + "Datei ", // note space at end + ", Filament ", // note space at end + ", Schicht ", // note space at end + "n/v", + "Pause", + "Baby Step", + "Fortsetzen", + "Abbrechen", + + // Setup page + "Lautstärke ", // note space at end + "Touch kalibrieren", + "Anzeige spiegeln", + "Farben invertieren", + "Thema", + "Helligkeit -", + "Helligkeit +", + "Einstellungen speichern", + "Einstellungen löschen", + "Speichern & Neustarten", + + // Misc + "Rücksetzen bestätigen", + "Neustart bestätigen", + "Löschen bestätigen", + "Sind sie sicher?", + "Berühren sie den Punkt", + "Einige Einstellungen sind nicht gespeichert!", + "Berühren sie Speichern & Neustarten um die neuen Einstellungen anzuwenden", + "Kopf bewegen", + "Extrusionsmenge (mm)", + "Geschwindigkeit (mm/s)", + "Extrudieren", + "Zurückziehen", + "Babystepping", + "Aktueller Z-Versatz: ", + "Nachricht", + "Nachrichten", + "Panel Due Firmwareversion ", // note space at end + + // File popup + "Dateien auf Karte ", // note the space on the end + "Makros", + "Fehler ", // note the space at the end + " beim Zugriff auf SD-Karte", // note the space at the start + "Dateiname: ", + "Größe: ", + "Schichthöhe: ", + "Objekthöhe: ", + "Benötigtes Filament: ", + "Erzeugt mit: ", + + // Printer status strings + { + "Verbinde", + "Leerlauf", + "Drucken", + "Angehalten", + "Starte", + "Pausiert", + "Beschäftigt", + "Pausiere", + "Fortsetzen", + "Firmware-Upload", + "Wechsle Tool" + }, + + // Theme names + { + "Hell", + "Dunkel" + } + }, + + // French + { + // Language name + "Français", + + // Main page strings + "Contrôle", + "Imprimer", + "Console", + "Installation", + "Actuel" THIN_SPACE DEGREE_SYMBOL "C", + "Actif" THIN_SPACE DEGREE_SYMBOL "C", + "Standby" THIN_SPACE DEGREE_SYMBOL "C", + "Mouvement", + "Extrusion", + "Macro", + "ARRÊT", + + // Print page + "Extrudeuse" THIN_SPACE "%", + "Vitesse ", // note space at end + "Ventilateur ", // note space at end + "Temps Restant: ", + "Fichier ", // note space at end + ", filament ", // note space at end + ", couche ", // note space at end + "n/a", + "Pause", + "Baby step", + "Reprise", + "Annuler", + + // Setup page + "Volume ", // note space at end + "Calibrer touch", + "Affichage en négatif", + "Inverser affichage", + "Théme", + "Luminosité -", + "Luminosité +", + "Sauver paramêtres", + "Effacer paramêtres", + "Sauvegarde & Redémarrage", + + // Misc + "Confirmer le réinitialisation de l'imprimante", + "Confirm Redémarrage", + "Confirm suppression fichier", + "Vous êtes sûre?", + "Appuyer sur le point", + "Certains réglages ne sont pas sauvegardés!", + "Appuyer sur Sauvegarde et Redémarrage pour utiliser les nouveaux réglages", + "Mouvement de la tête", + "Quantité de Matiére extrudée (mm)", + "Vitesse (mm/s)", + "Extruder", + "Retracter", + "Baby stepping", + "décalage Z courant : ", + "Message", + "Messages", + "Version du firmware du Panel Due ", // note space at end + + // File popup + "Fichier sur carte ", // note the space on the end + "Macros", + "Erreur ", // note the space at the end + " accés SD card en cours", // note the space at the start + "Nom du fichier : ", + "Taille : ", + "Hauteur de couche: ", + "Hauteur de l'objet: ", + "Filament requis: ", + "Sliced par: ", + + // Printer status strings + { + "Connection en cours", + "Au repos", + "Impression", + "Arrêt", + "Démarrage", + "Pause", + "Occupé" + "Pause", + "Reprise", + "Téleverser le firmware", + "Changement d'outil" + }, + + // Theme names + { + "Fond Blanc", + "Fond Noir" + } + }, + + // Russian + { + // Language name + "Русский", + + // Main page strings + "Управление", + "Печать", + "Консоль", + "Настройка", + "Текущ" THIN_SPACE DEGREE_SYMBOL "C", + "Активн" THIN_SPACE DEGREE_SYMBOL "C", + "Неакт" THIN_SPACE DEGREE_SYMBOL "C", + "Двигать", + "Выдавить", + "Макро", + "СТОП", + + // Print page + "Поток" THIN_SPACE "%", + "Темп" THIN_SPACE, // note space at end + "Обдув" THIN_SPACE, // note space at end + "Осталось: ", + "файл ", // note space at end + ", филамент ", // note space at end + ", слой ", // note space at end + "n/a", + "Пауза", + "Микрошаг", + "Продолжить", + "Отмена", + + // Setup page + "Громкость" THIN_SPACE, // note space at end + "Калибровка", + "Зеркально", + "Перевернуть", + "Тема", + "Яркость -", + "Яркость +", + "Сохранить", + "Сбросить", + "Сохран/Рестарт", + + // Misc + "Подтвердить заводской сброс", + "Подтвердить рестарт", + "Подтвердить удаление файла", + "Вы уверены?", + "Дотроньтесь до точки", + "Некоторые настройки не сохранены!", + "Нажмите Сохран/Рестарт для активации настроек", + "Двигать по осям", + "Выдавить пластик (мм)", + "Скорость (мм/с)", + "Выдавить", + "Откат", + "Микрошаг", + "Текущее смещение Z: ", + "Сообщение", + "Сообщения", + "Panel Due версия прошивки ", // note space at end + + // File popup + "На карте ", // note the space on the end + "Макро", + "Ошибка ", // note the space at the end + " доступ к SD карте", // note the space at the start + "Файл: ", + "Размер: ", + "Высота слоя: ", + "Высота объекта: ", + "Количество пластика: ", + "Слайсер: ", + + // Printer status strings + { + "Соединение", + "Простой", + "Печать", + "Сброс", + "Запуск печати", + "Остановлен", + "Занят", + "Останавливается", + "Возобновление", + "Загрузка прошивки", + "Смена инструмента" + }, + + // Theme names + { + "Светлая", + "Темная" + } + }, + +#if 0 // Spanish not supported yet + // Spanish + { + // Language name + "Espanol", + + // Main page strings + "Control", + "Print", + "Console", + "Setup", + "Current" THIN_SPACE DEGREE_SYMBOL "C", + "Active" THIN_SPACE DEGREE_SYMBOL "C", + "Standby" THIN_SPACE DEGREE_SYMBOL "C", + "Move", + "Extrusion", + "Macro", + "STOP", + + // Print page + "Extruder" THIN_SPACE "%", + "Speed ", // note space at end + "Fan ", // note space at end + "Time left: ", + "file ", // note space at end + ", filament ", // note space at end + ", layer ", // note space at end + "n/a", + "Pause", + "Baby step", + "Resume", + "Cancel", + + // Setup page + "Volume ", // note space at end + "Calibrate touch", + "Mirror display", + "Invert display", + "Theme", + "Brightness -", + "Brightness +", + "Save settings", + "Clear settings", + "Save & Restart", + + // Misc + "Confirm factory reset", + "Confirm restart", + "Confirm file delete", + "Are you sure?", + "Touch the spot", + "Some settings are not saved!", + "Touch Save & Restart to use new settings", + "Move head", + "Extrusion amount (mm)", + "Speed (mm/s)", + "Extrude", + "Retract", + "Baby stepping", + "Current Z offset: ", + "Message", + "Messages", + "Panel Due firmware version ", // note space at end + + // File popup + "Files on card ", // note the space on the end + "Macros", + "Error ", // note the space at the end + " accessing SD card", // note the space at the start + "Filename: ", + "Size: ", + "Layer height: ", + "Object height: ", + "Filament needed: ", + "Sliced by: ", + + // Printer status strings + { + "conexión", + "ocioso", + "imprimiendo", + "detuvo", + "empezando", + "pausado", + "ocupado", + "pausando", + "reanudando", + "carga del firmware", + "herramienta de cambio" + }, + + // Theme names + { + "Light", + "Dark" + } + }, +#endif +}; + +#endif /* SRC_STRINGS_HPP_ */ diff --git a/Inc/UTFT.h b/Inc/UTFT.h index 3dbc9ff..eb9ae0f 100644 --- a/Inc/UTFT.h +++ b/Inc/UTFT.h @@ -69,8 +69,10 @@ enum DisplayOrientation { // const DisplayOrientation DefaultDisplayOrientAdjust = static_cast(SwapXY | ReverseY | InvertBitmap); // const DisplayOrientation DefaultTouchOrientAdjust = static_cast(ReverseY); -// #define DISPLAY_X (320) -// #define DISPLAY_Y (240) + +#define DISPLAY_X (320) +#define DISPLAY_Y (240) + // const PixelNumber DisplayX = DISPLAY_X; // const PixelNumber DisplayY = DISPLAY_Y; diff --git a/Inc/UTouch.h b/Inc/UTouch.h new file mode 100644 index 0000000..e32633a --- /dev/null +++ b/Inc/UTouch.h @@ -0,0 +1,31 @@ +/* + UTouch.cpp - library support for Color TFT LCD Touch screens on SAM3X + Originally based on Utouch library by Henning Karlsen. + Rewritten by D Crocker using the approach described in TI app note http://www.ti.com/lit/pdf/sbaa036. +*/ + +#ifndef UTouch_h +#define UTouch_h + +#include "UTFT.h" + +class UTouch +{ +public: + void init(uint16_t xp, uint16_t yp, DisplayOrientation orientationAdjust = Default); + bool read(uint16_t &x, uint16_t &y, uint16_t * null rawX = nullptr, uint16_t * null rawY = nullptr); + void calibrate(uint16_t xlow, uint16_t xhigh, uint16_t ylow, uint16_t yhigh, uint16_t margin); + void adjustOrientation(DisplayOrientation a) { orientAdjust = (DisplayOrientation) (orientAdjust ^ a); } + DisplayOrientation getOrientation() const { return orientAdjust; } + +private: + DisplayOrientation orientAdjust; + uint16_t disp_x_size, disp_y_size; + uint16_t scaleX, scaleY; + int16_t offsetX, offsetY; + + bool getTouchData(bool wantY, uint16_t &rslt); + uint16_t diff(uint16_t a, uint16_t b) { return (a < b) ? b - a : a - b; } +}; + +#endif \ No newline at end of file diff --git a/Inc/UserInterface.h b/Inc/UserInterface.h new file mode 100644 index 0000000..036bbcc --- /dev/null +++ b/Inc/UserInterface.h @@ -0,0 +1,191 @@ +/* + * UserInterface.h + * + * Created on: 7 Jan 2017 + * Author: David + */ + +#ifndef SRC_USERINTERFACE_HPP_ +#define SRC_USERINTERFACE_HPP_ + +#include "UTFT.h" +#include "PrinterStatus.h" +#include "Display.h" + +typedef uint32_t FirmwareFeatures; + +#define noGcodesFolder 0x0001 // gcodes files are in 0:/ not 0:/gcodes +#define noStandbyTemps 0x0002 // firmware does not support separate tool active and standby temperatures +#define noG10Temps 0x0004 // firmware does not support using G10 to set temperatures +#define noDriveNumber 0x0008 // firmware does not handle drive numbers at the start of file paths +#define noM20M36 0x0010 // firmware does not handle M20 S2 or M36 commands. Use M408 S20 and M408 S36 instead. + +const size_t NumColourSchemes = 2; +const unsigned int maxHeaters = 5; +#define MAX_AXES (3) + +const PixelNumber margin = 2; +const PixelNumber textButtonMargin = 1; +const PixelNumber iconButtonMargin = 1; +const PixelNumber outlinePixels = 2; +const PixelNumber fieldSpacing = 6; +const PixelNumber statusFieldWidth = 156; +const PixelNumber bedColumn = 114; + +const PixelNumber rowTextHeight = 21; // height of the font we use +const PixelNumber rowHeight = 28; +const PixelNumber moveButtonRowSpacing = 12; +const PixelNumber extrudeButtonRowSpacing = 12; +const PixelNumber fileButtonRowSpacing = 8; +const PixelNumber keyboardButtonRowSpacing = 6; // small enough to show 2 lines of messages + +const PixelNumber speedTextWidth = 70; +const PixelNumber efactorTextWidth = 30; +const PixelNumber percentageWidth = 60; +const PixelNumber e1FactorXpos = 140, e2FactorXpos = 250; + +const PixelNumber messageTimeWidth = 60; + +const PixelNumber popupY = 192; +const PixelNumber popupSideMargin = 10; +const PixelNumber popupTopMargin = 10; +const PixelNumber keyboardTopMargin = 8; + +const PixelNumber popupFieldSpacing = 10; + +const PixelNumber axisLabelWidth = 26; +const PixelNumber firstMessageRow = margin + rowHeight + 3; // adjust this to get a whole number of message rows below the keyboard + +const PixelNumber progressBarHeight = 10; +const PixelNumber closeButtonWidth = 40; + +const PixelNumber touchCalibMargin = 15; + +extern uint8_t glcd19x21[]; // declare which fonts we will be using +#define DEFAULT_FONT glcd19x21 + +const PixelNumber buttonHeight = rowTextHeight + 4; +const PixelNumber tempButtonWidth = (DISPLAY_X + fieldSpacing - bedColumn)/maxHeaters - fieldSpacing; + +const PixelNumber row1 = 0; // we don't need a top margin +const PixelNumber row2 = row1 + rowHeight - 2; // the top row never has buttons so it can be shorter +const PixelNumber row3 = row2 + rowHeight; +const PixelNumber row4 = row3 + rowHeight; +const PixelNumber row5 = row4 + rowHeight; +const PixelNumber row6 = row5 + rowHeight; +const PixelNumber row6p3 = row6 + (rowHeight/3); +const PixelNumber row7 = row6 + rowHeight; +const PixelNumber row7p7 = row7 + ((2 * rowHeight)/3); +const PixelNumber row8 = row7 + rowHeight; +const PixelNumber row8p7 = row8 + ((2 * rowHeight)/3); +const PixelNumber row9 = row8 + rowHeight; +const PixelNumber rowTabs = DISPLAY_Y - rowTextHeight; // place at bottom of screen with no margin +const PixelNumber labelRowAdjust = 2; // how much to drop non-button fields to line up with buttons + +const PixelNumber speedColumn = margin; +const PixelNumber fanColumn = DISPLAY_X/4 + 20; + +const PixelNumber pauseColumn = DISPLAY_X/2 + 10 + fieldSpacing; +const PixelNumber resumeColumn = pauseColumn; +const PixelNumber cancelColumn = pauseColumn + (DISPLAY_X - pauseColumn - fieldSpacing - margin)/2 + fieldSpacing; +const PixelNumber babystepColumn = cancelColumn; + +const PixelNumber fullPopupWidth = DISPLAY_X - (2 * margin); +const PixelNumber fullPopupHeight = DISPLAY_X - (2 * margin); +const PixelNumber popupBarHeight = buttonHeight + (2 * popupTopMargin); + +const PixelNumber tempPopupBarWidth = (3 * fullPopupWidth)/4; +const PixelNumber fileInfoPopupWidth = fullPopupWidth - (4 * margin), + fileInfoPopupHeight = (8 * rowTextHeight) + buttonHeight + (2 * popupTopMargin); +const PixelNumber areYouSurePopupWidth = DISPLAY_X - 80, + areYouSurePopupHeight = (3 * rowHeight) + (2 * popupTopMargin); + +const PixelNumber movePopupWidth = fullPopupWidth; +const PixelNumber movePopupHeight = ((MAX_AXES + 1) * buttonHeight) + (MAX_AXES * moveButtonRowSpacing) + (2 * popupTopMargin); + +const PixelNumber extrudePopupWidth = fullPopupWidth; +const PixelNumber extrudePopupHeight = (5 * buttonHeight) + (4 * extrudeButtonRowSpacing) + (2 * popupTopMargin); + +const PixelNumber keyboardButtonWidth = DISPLAY_X/5; +const PixelNumber keyboardPopupWidth = fullPopupWidth; +const PixelNumber keyButtonWidth = (keyboardPopupWidth - 2 * popupSideMargin)/16; +const PixelNumber keyButtonHStep = (keyboardPopupWidth - 2 * popupSideMargin - keyButtonWidth)/11; +const PixelNumber keyButtonVStep = buttonHeight + keyboardButtonRowSpacing; +const PixelNumber keyboardPopupHeight = (5 * keyButtonVStep) + (2 * keyboardTopMargin) + buttonHeight; +const PixelNumber keyboardPopupY = margin; + +const unsigned int numFileColumns = 1; +const unsigned int numFileRows = (fullPopupHeight - (2 * popupTopMargin) + fileButtonRowSpacing)/(buttonHeight + fileButtonRowSpacing) - 1; +const unsigned int numDisplayedFiles = numFileColumns * numFileRows; +const PixelNumber fileListPopupWidth = fullPopupWidth; +const PixelNumber fileListPopupHeight = ((numFileRows + 1) * buttonHeight) + (numFileRows * fileButtonRowSpacing) + (2 * popupTopMargin); + +const uint32_t numMessageRows = (rowTabs - margin - rowHeight)/rowTextHeight; +const PixelNumber messageTextX = margin + messageTimeWidth + 2; +const PixelNumber messageTextWidth = DISPLAY_X - margin - messageTextX; + +const PixelNumber alertPopupWidth = fullPopupWidth - 6 * margin; +const PixelNumber alertPopupHeight = 3 * rowTextHeight + 2 * popupTopMargin; + +const PixelNumber babystepPopupWidth = (2 * fullPopupWidth)/3; +const PixelNumber babystepPopupHeight = 3 * rowHeight + 2 * popupTopMargin; +const PixelNumber babystepRowSpacing = rowHeight; + + +extern IntegerField *freeMem; +extern TextButton *filenameButtons[]; +extern StaticTextField *debugField; +extern StaticTextField *touchCalibInstruction; +extern StaticTextField *messageTextFields[], *messageTimeFields[]; +extern TextField *fwVersionField; + +namespace UI +{ + extern unsigned int GetNumLanguages(); + extern void CreateFields(uint32_t language, const ColourScheme& colours); + extern void CheckSettingsAreSaved(); + extern void ShowAxis(size_t axis, bool b); + extern void UpdateAxisPosition(size_t axis, float fval); + extern void UpdateCurrentTemperature(size_t heater, float fval) pre(heater < maxHeaters); + extern void ShowHeater(size_t heater, bool show) pre(heater < maxHeaters); + extern void UpdateHeaterStatus(size_t heater, int ival) pre(heater < maxHeaters); + extern void ChangeStatus(PrinterStatus oldStatus, PrinterStatus newStatus); + extern void UpdateTimesLeft(size_t index, unsigned int seconds); + extern bool ChangePage(ButtonBase *newTab); + extern bool DoPolling(); + extern void Spin(); + extern void PrintStarted(); + extern void PrintingFilenameChanged(const char data[]); + extern void ShowDefaultPage(); + extern void UpdatePrintingFields(); + extern void SetPrintProgressPercent(unsigned int percent); + extern void UpdateGeometry(unsigned int numAxes, bool isDelta); + extern void UpdateHomedStatus(int axis, bool isHomed); + extern void UpdateZProbe(const char data[]); + extern void UpdateMachineName(const char data[]); + extern void ProcessAlert(const char data[]); + extern void UpdateFileGeneratedByText(const char data[]); + extern void UpdateFileObjectHeight(float f); + extern void UpdateFileLayerHeight(float f); + extern void UpdateFileSize(int size); + extern void UpdateFileFilament(int len); + extern void UpdateFanPercent(int rpm); + extern void UpdateActiveTemperature(size_t index, int ival) pre(index < maxHeaters); + extern void UpdateStandbyTemperature(size_t index, int ival) pre(index < maxHeaters); + extern void UpdateExtrusionFactor(size_t index, int ival) pre(index + 1 < maxHeaters); + extern void UpdateSpeedPercent(int ival); + extern void FirmwareFeaturesChanged(FirmwareFeatures newFeatures); + extern void ProcessTouch(ButtonPress bp); + extern void ProcessTouchOutsidePopup(ButtonPress bp) + pre(bp.IsValid()); + extern void OnButtonPressTimeout(); + extern bool IsDisplayingFileInfo(); + extern void UpdateFilesListTitle(int cardNumber, unsigned int numVolumes, bool isFilesList); + extern void SetNumTools(unsigned int n); + extern void FileListLoaded(int errCode); + extern void EnableFileNavButtons(bool scrollEarlier, bool scrollLater, bool parentDir); + extern unsigned int GetNumScrolledFiles(); + extern void SetBabystepOffset(float f); +} + +#endif /* SRC_USERINTERFACE_HPP_ */ diff --git a/Inc/Vector.h b/Inc/Vector.h new file mode 100644 index 0000000..8b41dea --- /dev/null +++ b/Inc/Vector.h @@ -0,0 +1,226 @@ +/* + * Vector.h + * + * Created: 09/11/2014 09:50:41 + * Author: David + */ + + +#ifndef VECTOR_H_ +#define VECTOR_H_ + +#include // for size_t +#include +#include + +// #undef printf +// #undef scanf +// void printf(); // to keep gcc happy when we include cstdio +// void scanf(); // to keep gcc happy when we include cstdio +// #include + +// Bounded vector class +template class Vector +{ +public: + Vector() : filled(0) { } + + bool full() const { return filled == N; } + + size_t capacity() const { return N; } + + size_t size() const { return filled; } + + bool isEmpty() const { return filled == 0; } + + const T& operator[](size_t index) const pre(index < N) { return storage[index]; } + + T& operator[](size_t index) pre(index < N) { return storage[index]; } + + void add(const T& x) pre(filled < N) { storage[filled++] = x; } + + void add(const T* array p, size_t n) pre(filled + n <= N); + + void erase(size_t pos, size_t count = 1); + + void clear() { filled = 0; } + + const T* array c_ptr() { return storage; } + + void sort(bool (*sortfunc)(T, T)); + +protected: + T storage[N]; + size_t filled; +}; + +template void Vector::add(const T* array p, size_t n) +{ + while (n != 0 && filled != N) + { + storage[filled++] = *p++; + --n; + } +} + +template void Vector::sort(bool (*sortfunc)(T, T)) +{ + for (size_t i = 1; i < filled; ++i) + { + for (size_t j = 0; j < i; ++j) + { + if ((*sortfunc)(storage[j], storage[i])) + { + T temp = storage[i]; + // Insert element i just before element j + for (size_t k = i; k > j; --k) + { + storage[k] = storage[k - 1]; + } + storage[j] = temp; + } + } + } +} + +template void Vector::erase(size_t pos, size_t count) +{ + while (pos + count < filled) + { + storage[pos] = storage[pos + count]; + ++pos; + } + if (pos < filled) + { + filled = pos; + } +} + +// String class. This is like the vector class except that we always keep a null terminator so that we can call c_str() on it. +template class String : public Vector +{ +public: + String() : Vector() + { + this->clear(); + } + + String(const char* array s) : Vector() + { + this->clear(); + this->copy(s); + } + + // Redefine 'full' so as to make room for a null terminator + bool full() const { return this->filled == N; } + + // Redefine 'add' to add a null terminator + void add(char x) pre(this->filled < N) + { + this->storage[this->filled++] = x; + this->storage[this->filled] = '\0'; + } + + // Redefine 'erase' to preserve the null terminator + void erase(size_t pos, size_t count = 1) + { + static_cast*>(this)->erase(pos, count); + this->storage[this->filled] = '\0'; + } + + const char* array c_str() const { return this->storage; } + + void clear() + { + this->filled = 0; + this->storage[0] = '\0'; + } + + void catFrom(const char* s) + { + while (*s != '\0' && this->filled < N) + { + this->storage[this->filled++] = *s++; + } + this->storage[this->filled] = '\0'; + } + + void copy(const char* s) + { + this->clear(); + this->catFrom(s); + } + + template void copy(String s) + { + copy(s.c_str()); + } + + int printf(const char *fmt, ...); + + int catf(const char *fmt, ...); + + // Compare with a C string. If the C string is too long but the part of it we could accommodate matches, return true. + bool similar(const char* s) const + { + return strncmp(s, this->storage, N) == 0; + } + + // Compare with a C string + bool equals(const char* s) const + { + return strcmp(s, this->storage) == 0; + } + + bool equalsIgnoreCase(const char* s) const + { + return strcasecmp(s, this->storage) == 0; + } +}; + +template int String::printf(const char *fmt, ...) +{ + va_list vargs; + va_start(vargs, fmt); + int ret = vsnprintf(this->storage, N + 1, fmt, vargs); + va_end(vargs); + + if (ret < 0) + { + this->filled = 0; + this->storage[0] = 0; + } + else if (ret >= 0 && (size_t)ret < N) + { + this->filled = ret; + } + else + { + this->filled = N; + } + return ret; +} + +template int String::catf(const char *fmt, ...) +{ + va_list vargs; + va_start(vargs, fmt); + int ret = vsnprintf(this->storage + this->filled, N + 1 - this->filled, fmt, vargs); + va_end(vargs); + + if (ret < 0) + { + this->storage[this->filled] = 0; // in case snprintf messed it up + } + else if (this->filled + ret < N) + { + this->filled += ret; + } + else + { + this->filled = N; + } + return ret; +} + +#endif /* VECTOR_H_ */ \ No newline at end of file diff --git a/MKS-TFT32.ebp b/MKS-TFT32.ebp index 762a4d5..95a1686 100644 --- a/MKS-TFT32.ebp +++ b/MKS-TFT32.ebp @@ -224,22 +224,36 @@ + + + + + + + + + + + + + + @@ -336,6 +350,9 @@ + + @@ -345,10 +362,16 @@ + + + + - + @@ -375,9 +398,15 @@ + + + + diff --git a/Middlewares/Third_Party/FatFs/src/ff.c b/Middlewares/Third_Party/FatFs/src/ff.c index 1151b75..1f75ae1 100644 --- a/Middlewares/Third_Party/FatFs/src/ff.c +++ b/Middlewares/Third_Party/FatFs/src/ff.c @@ -568,7 +568,7 @@ static WCHAR LfnBuf[_MAX_LFN+1]; /* LFN enabled with static working buffer */ #define INIT_NAMBUF(fs) { lfn = ff_memalloc((_MAX_LFN+1)*2 + SZDIRE*19); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; (fs)->dirbuf = (BYTE*)(lfn+_MAX_LFN+1); } #define FREE_NAMBUF() ff_memfree(lfn) #else -#define DEF_NAMBUF WCHAR *lfn; +#define DEF_NAMBUF WCHAR *lfn __attribute__((unused)); #define INIT_NAMBUF(fs) { lfn = ff_memalloc((_MAX_LFN+1)*2); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; } #define FREE_NAMBUF() ff_memfree(lfn) #endif @@ -1270,7 +1270,7 @@ FRESULT remove_chain ( /* FR_OK(0):succeeded, !=0:error */ #if _FS_EXFAT || _USE_TRIM if (ecl + 1 == nxt) { /* Is next cluster contiguous? */ ecl = nxt; - } else { /* End of contiguous cluster block */ + } else { /* End of contiguous cluster block */ #if _FS_EXFAT if (fs->fs_type == FS_EXFAT) { res = change_bitmap(fs, scl, ecl - scl + 1, 0); /* Mark the cluster block 'free' on the bitmap */ @@ -1978,12 +1978,12 @@ FRESULT load_xdir ( /* FR_INT_ERR: invalid entry block */ } -#if !_FS_READONLY || _FS_RPATH != 0 +#if !_FS_READONLY || _FS_RPATH != 0 /*------------------------------------------------*/ /* exFAT: Load the object's directory entry block */ /*------------------------------------------------*/ static -FRESULT load_obj_dir ( +FRESULT load_obj_dir ( DIR* dp, /* Blank directory object to be used to access containing direcotry */ const _FDID* obj /* Object with containing directory information */ ) @@ -2869,7 +2869,7 @@ int get_ldnumber ( /* Returns logical drive number (-1:invalid drive) */ for (tt = *path; (UINT)*tt >= (_USE_LFN ? ' ' : '!') && *tt != ':'; tt++) ; /* Find ':' in the path */ if (*tt == ':') { /* If a ':' is exist in the path name */ tp = *path; - i = *tp++ - '0'; + i = *tp++ - '0'; if (i < 10 && tp == tt) { /* Is there a numeric drive id? */ if (i < _VOLUMES) { /* If a drive id is found, get the value and strip it */ vol = (int)i; diff --git a/Src/Display.cpp b/Src/Display.cpp new file mode 100644 index 0000000..f1d34aa --- /dev/null +++ b/Src/Display.cpp @@ -0,0 +1,818 @@ +/* + * Display.cpp + * + * Created: 04/11/2014 09:42:47 + * Author: David + */ + +#include "Display.h" +#include "Icons.h" + +#undef min +#undef max +#undef array +#undef result +#include +#define array _ecv_array +#define result _ecv_result + +extern UTFT lcd; + +// Static fields of class DisplayField +LcdFont DisplayField::defaultFont = nullptr; +Colour DisplayField::defaultFcolour = white; +Colour DisplayField::defaultBcolour = black; +Colour DisplayField::defaultButtonBorderColour = black; +Colour DisplayField::defaultGradColour = 0; +Colour DisplayField::defaultPressedBackColour = black; +Colour DisplayField::defaultPressedGradColour = 0; +Palette DisplayField::defaultIconPalette = IconPaletteLight; + +DisplayField::DisplayField(PixelNumber py, PixelNumber px, PixelNumber pw) + : y(py), x(px), width(pw), fcolour(defaultFcolour), bcolour(defaultBcolour), + changed(true), visible(true), underlined(false), textRows(1), next(nullptr) +{ +} + +void DisplayField::SetTextRows(const char * array null t) +{ + unsigned int rows = 1; + if (t != nullptr) + { + while (*t != 0) + { + if (*t == '\n') + { + ++rows; + } + ++t; + } + } + textRows = rows; +} + +/*static*/ void DisplayField::SetDefaultColours(Colour pf, Colour pb, Colour pbb, Colour pg, Colour pbp, Colour pgp, Palette pal) +{ + defaultFcolour = pf; + defaultBcolour = pb; + defaultButtonBorderColour = pbb; + defaultGradColour = pg; + defaultPressedBackColour = pbp; + defaultPressedGradColour = pgp; + defaultIconPalette = pal; +} + +/*static*/ PixelNumber DisplayField::GetTextWidth(const char* array s, PixelNumber maxWidth) +{ + lcd.setTextPos(0, 9999, maxWidth); + lcd.print(s); // dummy print to get text width + return lcd.getTextX(); +} + +void DisplayField::Show(bool v) +{ + if (visible != v) + { + visible = changed = v; + } +} + +// Find the best match to a touch event in a list of fields +ButtonPress DisplayField::FindEvent(PixelNumber x, PixelNumber y, DisplayField * null p) +{ + const int maxXerror = 8, maxYerror = 8; // set these to how close we need to be + int bestError = maxXerror + maxYerror; + ButtonPress best;; + while (p != nullptr) + { + if (p->visible && p->GetEvent() != nullEvent) + { + int xError = (x < p->GetMinX()) ? p->GetMinX() - x + : (x > p->GetMaxX()) ? x - p->GetMaxX() + : 0; + if (xError < maxXerror) + { + int yError = (y < p->GetMinY()) ? p->GetMinY() - y + : (y > p->GetMaxY()) ? y - p->GetMaxY() + : 0; + if (yError < maxYerror && xError + yError < bestError) + { + bestError = xError + yError; + best = ButtonPress(static_cast(p), 0); + } + } + } + p = p->next; + } + return best; +} + +void DisplayField::SetColours(Colour pf, Colour pb) +{ + if (fcolour != pf || bcolour != pb) + { + fcolour = pf; + bcolour = pb; + changed = true; + } +} + +// ButtonPress class methods +ButtonPress::ButtonPress() : button(nullptr), index(0) { } + +ButtonPress::ButtonPress(ButtonBase *b, unsigned int pi) : button(b), index(pi) { } + +void ButtonPress::Clear() +{ + button = nullptr; + index = 0; +} + +event_t ButtonPress::GetEvent() const +{ + return button->GetEvent(); +} + +int ButtonPress::GetIParam() const +{ + return button->GetIParam(index); +} + +const char* array ButtonPress::GetSParam() const +{ + return button->GetSParam(index); +} + +bool ButtonPress::operator==(const ButtonPress& other) const { return button == other.button && index == other.index; } + +// Window class methods +Window::Window(Colour pb) + : root(nullptr), next(nullptr), backgroundColour(pb) +{ +} + +// Append a field to the list of displayed fields +void Window::AddField(DisplayField *d) +{ + d->next = root; + root = d; +} + +bool Window::ObscuredByPopup(const DisplayField *p) const +{ + return next != nullptr + && ( ( p->GetMaxY() >= next->Ypos() && p->GetMinY() < next->Ypos() + next->GetHeight() + && p->GetMaxX() >= next->Xpos() && p->GetMinX() < next->Xpos() + next->GetWidth() + ) + || next->ObscuredByPopup(p) + ); +} + +bool Window::Visible(const DisplayField *p) const +{ + return p->IsVisible() && !ObscuredByPopup(p); +} + +// Get the field that has been touched, or nullptr if we can't find one +ButtonPress Window::FindEvent(PixelNumber x, PixelNumber y) +{ + return (x < Xpos() || y < Ypos()) ? ButtonPress() + : (next != nullptr) ? next->FindEvent(x, y) + : DisplayField::FindEvent(x - Xpos(), y - Ypos(), root); +} + +// Get the field that has been touched, but search only outside the popup +ButtonPress Window::FindEventOutsidePopup(PixelNumber x, PixelNumber y) +{ + if (next == nullptr) return ButtonPress(); + + ButtonPress f = DisplayField::FindEvent(x, y, root); + return (f.IsValid() && Visible(f.GetButton())) ? f : ButtonPress(); +} + +void Window::SetPopup(PopupWindow * p, PixelNumber px, PixelNumber py, bool redraw) +{ + if (px == AutoPlace) + { + px = (DISPLAY_X - p->GetWidth())/2; + } + if (py == AutoPlace) + { + py = (DISPLAY_Y - p->GetHeight())/2; + } + p->SetPos(px, py); + Window *pw = this; + while (pw->next != nullptr) + { + if (pw->next == p) + { + if (redraw) + { + p->Refresh(true); + } + return; // popup is already displayed + } + pw = pw->next; + } + p->next = nullptr; // ensure no nested popup + pw->next = p; + if (redraw) + { + p->Refresh(true); + } +} + +void Window::ClearPopup(bool redraw, PopupWindow *whichOne) +{ + if (next != nullptr) + { + // Find the penultimate window + Window *pw = this; + while (pw->next->next != nullptr) + { + pw = pw->next; + } + + if (whichOne == nullptr || whichOne == pw->next) + { + const PixelNumber xmin = pw->next->Xpos(), xmax = xmin + pw->next->GetWidth() - 1, ymin = pw->next->Ypos(), ymax = ymin + pw->next->GetHeight() - 1; + bool popupWasContained = pw->Contains(xmin, ymin, xmax, ymax); + if (popupWasContained) + { + // Clear the area that was occupied by the last window to the background colour of the penultimate window + lcd.setColor(pw->backgroundColour); + lcd.fillRoundRect(xmin, ymin, xmax, ymax); + } + + // Detach the last window + pw->next = nullptr; + + if (redraw) + { + if (popupWasContained) + { + // Re-display the fields of the penultimate window that were obscured + for (DisplayField * null pp = pw->root; pp != nullptr; pp = pp->next) + { + if (pp->IsVisible()) + { + pp->Refresh(true, pw->Xpos(), pw->Ypos()); + } + } + } + else + { + Refresh(true); // redraw everything + } + } + } + } +} + +// Redraw the specified field +void Window::Redraw(DisplayField *f) +{ + for (DisplayField * null p = root; p != nullptr; p = p->next) + { + if (p == f) + { + // The field belongs to this window + if (!ObscuredByPopup(p)) + { + if (p->IsVisible()) + { + p->Refresh(true, Xpos(), Ypos()); + } + else + { + lcd.setColor(backgroundColour); + lcd.fillRect(p->GetMinX() + Xpos(), p->GetMinY() + Ypos(), p->GetMaxX() + Xpos(), p->GetMaxY() + Ypos()); + } + } + return; + } + } + + // Else we didn't find the field in our window, so look in nested windows + if (next != nullptr) + { + next->Redraw(f); + } +} + +void Window::Show(DisplayField * null f, bool v) +{ + if (f != nullptr && f->IsVisible() != v) + { + f->Show(v); + + // Check whether the field is currently in the display list, if so then show or hide it + for (DisplayField *p = root; p != nullptr; p = p->next) + { + if (p == f) + { + if (ObscuredByPopup(f)) + { + // nothing to do + } + else if (v) + { + f->Refresh(true, Xpos(), Ypos()); + } + else + { + lcd.setColor(backgroundColour); + lcd.fillRect(f->GetMinX(), f->GetMinY(), f->GetMaxX(), f->GetMaxY()); + } + return; + } + } + + // Else we didn't find it, so maybe it is in a popup field + if (next != nullptr) + { + next->Redraw(f); + } + } +} + +// Show the button as pressed or not +void Window::Press(ButtonPress bp, bool v) +{ + if (bp.IsValid()) + { + bp.GetButton()->Press(v, bp.GetIndex()); + if (bp.GetButton()->IsVisible()) // need to check this in case we are releasing the button and it has gone invisible since we pressed it + { + Redraw(bp.GetButton()); + } + } +} + +MainWindow::MainWindow() : Window(black), staticLeftMargin(0) +{ +} + +void MainWindow::Init(Colour bc) +{ + backgroundColour = bc; +} + +// Refresh all fields. If 'full' is true then we rewrite them all, else we just rewrite those that have changed. +void MainWindow::Refresh(bool full) +{ + if (full) + { + lcd.fillScr(backgroundColour, staticLeftMargin); + } + + for (DisplayField * null pp = root; pp != nullptr; pp = pp->next) + { + if (Visible(pp)) + { + pp->Refresh(full, 0, 0); + } + } + if (next != nullptr) + { + next->Refresh(full); + } +} + +bool MainWindow::Contains(PixelNumber xmin, PixelNumber ymin, PixelNumber xmax, PixelNumber ymax) const +{ + UNUSED(xmin); UNUSED(ymin); UNUSED(xmax); UNUSED(ymax); + return true; +} + +void MainWindow::ClearAllPopups() +{ + while (next != nullptr) + { + ClearPopup(true); + } +} + +PopupWindow::PopupWindow(PixelNumber ph, PixelNumber pw, Colour pb, Colour pBorder) + : Window(pb), height(ph), width(pw), borderColour(pBorder) +{ +} + +void PopupWindow::Refresh(bool full) +{ + if (full) + { + // Draw a rectangle inside the border + lcd.setColor(backgroundColour); + lcd.fillRoundRect(xPos + 1, yPos + 2, xPos + width - 2, yPos + height - 3); + + // Draw a double border + lcd.setColor(borderColour); + lcd.drawRoundRect(xPos, yPos, xPos + width - 1, yPos + height - 1); + lcd.drawRoundRect(xPos + 1, yPos + 1, xPos + width - 2, yPos + height - 2); + } + + for (DisplayField * null p = root; p != nullptr; p = p->next) + { + if (p->IsVisible() && (full || !ObscuredByPopup(p))) + { + p->Refresh(full, xPos, yPos); + } + } + + if (next != nullptr) + { + next->Refresh(full); + } +} + +bool PopupWindow::Contains(PixelNumber xmin, PixelNumber ymin, PixelNumber xmax, PixelNumber ymax) const +{ + return xPos + 2 <= xmin && yPos + 2 <= ymin && xPos + width >= xmax + 3 && yPos + height >= ymax + 3; +} + +PixelNumber FieldWithText::GetHeight() const +{ + PixelNumber height = UTFT::GetFontHeight(font) * textRows; + height += (textRows - 1) * 2; // 2px space between lines + if (underlined) + { + height += 2; // one space and the underline + } + if (border) + { + height += 4; // one space abd border top and bottom + } + return height; +} + +void FieldWithText::Refresh(bool full, PixelNumber xOffset, PixelNumber yOffset) +{ + if (full || changed) + { + xOffset += x; + yOffset += y; + PixelNumber textWidth = width; + if (border) + { + if (full) + { + lcd.setColor(fcolour); + lcd.drawRect(xOffset, yOffset, xOffset + width - 1, yOffset + GetHeight() - 1); + } + xOffset += 2; + yOffset += 2; + textWidth -= 4; + } + + lcd.setFont(font); + lcd.setColor(fcolour); + lcd.setBackColor(bcolour); + + // Do a dummy print to get the text width. Needed for underlining and for centre- or right-aligned text. + lcd.setTextPos(0, 9999, textWidth); + PrintText(); + const PixelNumber actualWidth = lcd.getTextX(); + const PixelNumber underlineY = yOffset + UTFT::GetFontHeight(font) + 1; + if (underlined) + { + // Remove previous underlining + lcd.setColor(bcolour); + lcd.drawLine(xOffset, underlineY, xOffset + textWidth - 1, underlineY); + lcd.setColor(fcolour); + } + + lcd.setTextPos(xOffset, yOffset, xOffset + textWidth); + if (align == TextAlignment::Left) + { + PrintText(); + lcd.clearToMargin(); + if (underlined) + { + lcd.drawLine(xOffset, underlineY, xOffset + actualWidth, underlineY); + } + } + else + { + lcd.clearToMargin(); + PixelNumber spare = textWidth - actualWidth; + if (align == TextAlignment::Centre) + { + const PixelNumber textX = xOffset + spare/2; + lcd.setTextPos(textX, yOffset, xOffset + textWidth); + PrintText(); + if (underlined) + { + lcd.drawLine(textX, underlineY, textX + actualWidth - 1, underlineY); + } + } + else + { + // Must be right aligned. Try to add a right margin of up to 3 pixels for better appearance. + if (spare <= 3) + { + spare = 0; + } + else + { + spare -= 3; + } + const PixelNumber textX = xOffset + spare; + lcd.setTextPos(textX, yOffset, xOffset + textWidth); + PrintText(); + if (underlined) + { + lcd.drawLine(textX, underlineY, textX + actualWidth, underlineY); + } + } + } + changed = false; + } +} + +void TextField::PrintText() const +{ + if (label != nullptr) + { + lcd.print(label); + } + if (text != nullptr) + { + lcd.print(text); + } +} + +void FloatField::PrintText() const +{ + if (label != nullptr) + { + lcd.print(label); + } + lcd.print(val, numDecimals); + if (units != nullptr) + { + lcd.print(units); + } +} + +void IntegerField::PrintText() const +{ + if (label != nullptr) + { + lcd.print(label); + } + lcd.print(val); + if (units != nullptr) + { + lcd.print(units); + } +} + +void StaticTextField::PrintText() const +{ + if (text != nullptr) + { + lcd.print(text); + } +} + +ButtonBase::ButtonBase(PixelNumber py, PixelNumber px, PixelNumber pw) + : DisplayField(py, px, pw), + borderColour(defaultButtonBorderColour), gradColour(defaultGradColour), + pressedBackColour(defaultPressedBackColour), pressedGradColour(defaultPressedGradColour), evt(nullEvent), pressed(false) +{ +} + +PixelNumber ButtonBase::textMargin = 1; +PixelNumber ButtonBase::iconMargin = 1; + +void ButtonBase::DrawOutline(PixelNumber xOffset, PixelNumber yOffset, bool isPressed) const +{ + lcd.setColor((isPressed) ? pressedBackColour : bcolour); + // Note that we draw the filled rounded rectangle with the full width but 2 pixels less height than the border. + // This means that we start with the requested colour inside the border. + lcd.fillRoundRect(x + xOffset, y + yOffset + 1, x + xOffset + width - 1, y + yOffset + GetHeight() - 2, (isPressed) ? pressedGradColour : gradColour, buttonGradStep); + lcd.setColor(borderColour); + lcd.drawRoundRect(x + xOffset, y + yOffset, x + xOffset + width - 1, y + yOffset + GetHeight() - 1); +} + +SingleButton::SingleButton(PixelNumber py, PixelNumber px, PixelNumber pw) + : ButtonBase(py, px, pw) +{ + param.sParam = nullptr; +} + +void SingleButton::DrawOutline(PixelNumber xOffset, PixelNumber yOffset) const +{ + ButtonBase::DrawOutline(xOffset, yOffset, pressed); +} + +/*static*/ LcdFont ButtonWithText::font; + +PixelNumber ButtonWithText::GetHeight() const +{ + PixelNumber ret = (UTFT::GetFontHeight(font) + 2) * textRows - 2; // height of the text + ret += 2 * textMargin + 2; // add the border height + return ret; +} + +void ButtonWithText::Refresh(bool full, PixelNumber xOffset, PixelNumber yOffset) +{ + if (full || changed) + { + DrawOutline(xOffset, yOffset); + lcd.setTransparentBackground(true); + lcd.setColor(fcolour); + lcd.setFont(font); + unsigned int rowsLeft = textRows; + size_t offset = 0; + PixelNumber rowY = y + yOffset + textMargin + 1; + do + { + lcd.setTextPos(0, 9999, width - 6); + PrintText(offset); // dummy print to get text width + PixelNumber spare = width - 6 - lcd.getTextX(); + lcd.setTextPos(x + xOffset + 3 + spare/2, rowY, x + xOffset + width - 3); // text is always centre-aligned + offset += PrintText(offset) + 1; + rowY += UTFT::GetFontHeight(font) + 2; + } while (--rowsLeft != 0); + lcd.setTransparentBackground(false); + changed = false; + } +} + +CharButton::CharButton(PixelNumber py, PixelNumber px, PixelNumber pw, char pc, event_t e) + : ButtonWithText(py, px, pw) +{ + SetEvent(e, (int)pc); +} + +size_t CharButton::PrintText(size_t offset) const +{ + UNUSED(offset); + return lcd.write((char)GetIParam(0)); +} + +TextButton::TextButton(PixelNumber py, PixelNumber px, PixelNumber pw, const char * array null pt, event_t e, int param) + : ButtonWithText(py, px, pw), text(pt) +{ + SetTextRows(pt); + SetEvent(e, param); +} + +TextButton::TextButton(PixelNumber py, PixelNumber px, PixelNumber pw, const char * array null pt, event_t e, const char * array param) + : ButtonWithText(py, px, pw), text(pt) +{ + SetEvent(e, param); +} + +size_t TextButton::PrintText(size_t offset) const +{ + if (text != nullptr) + { + return lcd.print(text + offset); + } + return 0; +} + +IconButton::IconButton(PixelNumber py, PixelNumber px, PixelNumber pw, Icon ic, event_t e, int param) + : SingleButton(py, px, pw), icon(ic) +{ + SetEvent(e, param); +} + +IconButton::IconButton(PixelNumber py, PixelNumber px, PixelNumber pw, Icon ic, event_t e, const char * array param) +: SingleButton(py, px, pw), icon(ic) +{ + SetEvent(e, param); +} + +void IconButton::Refresh(bool full, PixelNumber xOffset, PixelNumber yOffset) +{ + if (full || changed) + { + DrawOutline(xOffset, yOffset); + const uint16_t sx = GetIconWidth(icon), sy = GetIconHeight(icon); + lcd.setTransparentBackground(true); + lcd.drawBitmap4(xOffset + x + (width - sx)/2, yOffset + y + iconMargin + 1, sx, sy, GetIconData(icon), defaultIconPalette); + lcd.setTransparentBackground(false); + changed = false; + } +} + +size_t IntegerButton::PrintText(size_t offset) const +{ + UNUSED(offset); + size_t ret = 0; + if (label != nullptr) + { + ret += lcd.print(label); + } + ret += lcd.print(val); + if (units != nullptr) + { + ret += lcd.print(units); + } + return ret; +} + +size_t FloatButton::PrintText(size_t offset) const +{ + UNUSED(offset); + size_t ret = lcd.print(val, numDecimals); + if (units != nullptr) + { + ret += lcd.print(units); + } + return ret; +} + +#if 0 // not used yet +ButtonRow::ButtonRow(PixelNumber py, PixelNumber px, PixelNumber pw, PixelNumber ps, unsigned int nb, event_t e) + : ButtonBase(py, px, pw), numButtons(nb), whichPressed(-1), step(ps) +{ + evt = e; +} + +ButtonRowWithText::ButtonRowWithText(PixelNumber py, PixelNumber px, PixelNumber pw, PixelNumber ps, unsigned int nb, event_t e) + : ButtonRow(py, px, pw, ps, nb, e) +{ +} + +void ButtonRowWithText::Refresh(bool full, PixelNumber xOffset, PixelNumber yOffset) +{ + if (full || changed) + { + for (unsigned int i = 0; i < numButtons; ++i) + { + const PixelNumber buttonXoffset = xOffset + i * step; + DrawOutline(buttonXoffset, yOffset, (int)i == whichPressed); + lcd.setTransparentBackground(true); + lcd.setColor(fcolour); + lcd.setFont(font); + lcd.setTextPos(0, 9999, width - 6); + PrintText(i); // dummy print to get text width + PixelNumber spare = width - 6 - lcd.getTextX(); + lcd.setTextPos(x + buttonXoffset + 3 + spare/2, y + yOffset + textMargin + 1, x + xOffset + width - 3); // text is always centre-aligned + PrintText(i); + lcd.setTransparentBackground(false); + } + changed = false; + } +} + +void CharButtonRow::PrintText(unsigned int n) const +{ + lcd.write(text[n]); +} + +CharButtonRow::CharButtonRow(PixelNumber py, PixelNumber px, PixelNumber pw, PixelNumber ps, const char * array s, event_t e) + : ButtonRowWithText(py, px, pw, ps, strlen(s), e), text(s) +{ +} +#endif + +void ProgressBar::Refresh(bool full, PixelNumber xOffset, PixelNumber yOffset) +{ + if (full || changed) + { + PixelNumber pixelsSet = ((width - 2) * percent)/100; + if (full) + { + lcd.setColor(fcolour); + lcd.drawLine(x + xOffset, y, x + xOffset + width - 1, y + yOffset); + lcd.drawLine(x + xOffset, y + yOffset + height - 1, x + xOffset + width - 1, y + yOffset + height - 1); + lcd.drawLine(x + xOffset + width - 1, y + yOffset + 1, x + xOffset + width - 1, y + yOffset + height - 2); + + lcd.fillRect(x + xOffset, y + yOffset + 1, x + xOffset + pixelsSet, y + yOffset + height - 2); + if (pixelsSet < width - 2) + { + lcd.setColor(bcolour); + lcd.fillRect(x + xOffset + pixelsSet + 1, y + yOffset + 1, x + xOffset + width - 2, y + yOffset + height - 2); + } + } + else if (pixelsSet > lastNumPixelsSet) + { + lcd.setColor(fcolour); + lcd.fillRect(x + xOffset + lastNumPixelsSet, y + yOffset + 1, x + xOffset + pixelsSet, y + yOffset + height - 2); + } + else if (pixelsSet < lastNumPixelsSet) + { + lcd.setColor(bcolour); + lcd.fillRect(x + xOffset + pixelsSet + 1, y + yOffset + 1, x + xOffset + lastNumPixelsSet, y + yOffset + height - 2); + } + changed = false; + lastNumPixelsSet = pixelsSet; + } +} + +void StaticImageField::Refresh(bool full, PixelNumber xOffset, PixelNumber yOffset) +{ + if (full || changed) + { + lcd.drawCompressedBitmap(x + xOffset, y + yOffset, width, height, data); + changed = false; + } +} + +// End diff --git a/Src/MessageLog.cpp b/Src/MessageLog.cpp new file mode 100644 index 0000000..c12805b --- /dev/null +++ b/Src/MessageLog.cpp @@ -0,0 +1,221 @@ +/* + * MessageLog.cpp + * + * Created: 15/11/2015 10:54:42 + * Author: David + */ + +#include "ecv.h" +#include "MessageLog.h" +#include "UserInterface.h" +#include "Misc.h" +#include "Vector.h" + +namespace MessageLog +{ + const unsigned int maxMessageChars = 80; + + struct Message + { + static const size_t rttLen = 5; // number of chars we print for the message age + uint32_t receivedTime; + char receivedTimeText[rttLen]; // 5 characters plus null terminator + char msg[maxMessageChars + 1]; + }; + + static Message messages[numMessageRows + 1]; // one extra slot for receiving new messages into + static unsigned int messageStartRow = 0; // the row number at the top + static unsigned int newMessageStartRow = 0; // the row number that we put a new message in + + void Init() + { + // Clear the message log + for (size_t i = 0; i <= numMessageRows; ++i) // note we have numMessageRows+1 message slots + { + messages[i].receivedTime = 0; + messages[i].msg[0] = 0; + } + + UpdateMessages(true); + } + + // Update the messages on the message tab. If 'all' is true we do the times and the text, else we just do the times. + void UpdateMessages(bool all) + { + size_t index = messageStartRow; + for (size_t i = 0; i < numMessageRows; ++i) + { + Message *m = &messages[index]; + uint32_t tim = m->receivedTime; + char* p = m->receivedTimeText; + if (tim == 0) + { + p[0] = 0; + } + else + { + uint32_t age = (HAL_GetTick() - tim)/1000; // age of message in seconds + if (age < 10 * 60) + { + snprintf(p, Message::rttLen, "%lum%02lu", age/60, age%60); + } + else + { + age /= 60; // convert to minutes + if (age < 60) + { + snprintf(p, Message::rttLen, "%lum", age); + } + else if (age < 10 * 60) + { + snprintf(p, Message::rttLen, "%luh%02lu", age/60, age%60); + } + else + { + age /= 60; // convert to hours + if (age < 10) + { + snprintf(p, Message::rttLen, "%luh", age); + } + else if (age < 24 + 10) + { + snprintf(p, Message::rttLen, "%lud%02lu", age/24, age%24); + } + else + { + snprintf(p, Message::rttLen, "%lud", age/24); + } + } + } + } + messageTimeFields[i]->SetValue(p); + + if (all) + { + messageTextFields[i]->SetValue(m->msg); + } + index = (index + 1) % (numMessageRows + 1); + } + } + + // Add a message to the end of the list. It will be just off the visible part until we scroll it in. + void AppendMessage(const char* array data) + { + // Skip any leading spaces, we don't have room on the display to waste + while (*data == ' ') + { + ++data; + } + + // Discard empty messages + if (*data != 0) + { + bool split; + unsigned int numLines = 0; + do + { + ++numLines; + size_t msgRow = (messageStartRow + numLines + numMessageRows - 1) % (numMessageRows + 1); + size_t splitPoint; + + // See if the rest of the message will fit on one line + if (numLines == numMessageRows) + { + split = false; // if we have printed the maximum number of rows, don't split any more, just truncate + } + else + { + splitPoint = FindSplitPoint(data, maxMessageChars, messageTextWidth); + split = data[splitPoint] != '\0'; + } + + if (split) + { + safeStrncpy(messages[msgRow].msg, data, splitPoint + 1); + data += splitPoint; + if (data[0] == ' ') + { + ++data; // if we split just before a space, don't show the space + } + } + else + { + safeStrncpy(messages[msgRow].msg, data, maxMessageChars + 1); + } + + messages[msgRow].receivedTime = (numLines == 1) ? HAL_GetTick() : 0; + } while (split && data[0] != '\0'); + + newMessageStartRow = (messageStartRow + numLines) % (numMessageRows + 1); + } + } + + // If there is a new message, scroll it in + void DisplayNewMessage() + { + if (newMessageStartRow != messageStartRow) + { + messageStartRow = newMessageStartRow; + UpdateMessages(true); + } + } + + // This is called when we receive a new response from the host, which may or may not include a new message for the log + void BeginNewMessage() + { + newMessageStartRow = messageStartRow; + } + + // Find where we need to split a text string so that it will fit in a field + size_t FindSplitPoint(const char * array s, size_t maxChars, PixelNumber width) + { + const size_t remLength = strlen(s); + maxChars = min(maxChars, maxMessageChars); + if (remLength > maxChars || DisplayField::GetTextWidth(s, width + 1) > width) + { + // We need to split the line, so find out where + size_t low = 0, high = min(remLength, maxChars + 1); + while (low + 1 < high) + { + size_t mid = (low + high)/2; + char buf[maxMessageChars + 1]; + safeStrncpy(buf, s, mid + 1); + if (DisplayField::GetTextWidth(buf, messageTextWidth + 1) <= messageTextWidth) + { + low = mid; + } + else + { + high = mid; + } + } + + // The first 'low' characters fit, but no more. + // Look for a space or other character where we can split the line neatly + size_t splitPoint = low; + if (s[splitPoint] != ' ') + { + while (splitPoint > 0) + { + if (s[splitPoint - 1] == ' ' || s[splitPoint - 1] == ',') + { + // We can split after space or comma + break; + } + if ((low - splitPoint) * 5 > low) + { + // If there is no good split point within 1/5 of ther most that will fit, split anyway + splitPoint = low; + break; + } + --splitPoint; + } + } + return splitPoint; + } + return remLength; + } + +} // end namespace + +// End diff --git a/Src/Misc.cpp b/Src/Misc.cpp new file mode 100644 index 0000000..46c8566 --- /dev/null +++ b/Src/Misc.cpp @@ -0,0 +1,35 @@ +/* + * Misc.cpp + * + * Created: 14/11/2014 19:58:50 + * Author: David + */ + +#include "ecv.h" +#include "Misc.h" + +// Safe version of strncpy that ensures that the destination is always null-terminated on return +void safeStrncpy(char* array dst, const char* array src, size_t n) +{ + while (*src != 0 && n > 1) + { + *dst++ = *src++; + --n; + } + *dst = 0; +} + +// Return true if string a is the same as or starts with string b +bool stringStartsWith(const char* array a, const char* array b) +{ + while (*b != 0) + { + if (*a++ != *b++) + { + return false; + } + } + return true; +} + +// End diff --git a/Src/RequestTimer.cpp b/Src/RequestTimer.cpp new file mode 100644 index 0000000..e7c96a5 --- /dev/null +++ b/Src/RequestTimer.cpp @@ -0,0 +1,46 @@ +/* + * RequestTimer.cpp + * + * Created: 06/11/2015 14:22:55 + * Author: David + */ + +#include "ecv.h" +#include "RequestTimer.h" +#include "Hardware/SerialIo.hpp" + +extern bool OkToSend(); // in PanelDue.cpp + +RequestTimer::RequestTimer(uint32_t del, const char * array cmd, const char * array null ex) + : delayTime(del), command(cmd), extra(ex) +{ + timerState = stopped; +} + +bool RequestTimer::Process() +{ + if (timerState == running) + { + uint32_t now = HAL_GetTick(); + if (now - startTime > delayTime) + { + timerState = ready; + } + } + + if (timerState == ready && OkToSend()) + { + SerialIo::SendString(command); + if (extra != nullptr) + { + SerialIo::SendString(not_null(extra)); + } + SerialIo::SendChar('\n'); + startTime = SystemTick::GetTickCount(); + timerState = running; + return true; + } + return false; +} + +// End diff --git a/Src/SerialIo.cpp b/Src/SerialIo.cpp new file mode 100644 index 0000000..44f8544 --- /dev/null +++ b/Src/SerialIo.cpp @@ -0,0 +1,680 @@ +/* + * SerialIo.cpp + * + * Created: 09/11/2014 09:20:26 + * Author: David + */ + + +#include "ecv.h" +#include "stm32f1xx_hal.h" +#include "SerialIo.h" +#include "Vector.h" +#include "PanelDue.h" + +extern UART_HandleTypeDef huart2; + +namespace SerialIo +{ + static unsigned int lineNumber = 0; + + const char* array trGrave = "A\xC0" "E\xC8" "I\xCC" "O\xD2" "U\xD9" "a\xE0" "e\xE8" "i\xEC" "o\xF2" "u\xF9" ; + const char* array trAcute = "A\xC1" "E\xC9" "I\xCD" "O\xD3" "U\xDA" "a\xE1" "e\xE9" "i\xED" "o\xF3" "u\xFA" "y\xFD"; + const char* array trCircumflex = "A\xC2" "E\xCA" "I\xCE" "O\xD4" "U\xDB" "a\xE2" "e\xEA" "i\xEE" "o\xF4" "u\xFB" ; + const char* array trTilde = "A\xC3" "N\xD1" "O\xD5" "a\xE3" "o\xF5" ; + const char* array trUmlaut = "A\xC4" "E\xCB" "I\xCF" "O\xD6" "U\xDC" "a\xE4" "e\xEB" "i\xEF" "o\xF6" "u\xFC" "y\xFF"; + const char* array trCircle = "A\xC5" "a\xE5" ; + const char* array trCedilla = "C\xC7" "c\xE7"; + + // Initialize the serial I/O subsystem, or re-initialize it with a new baud rate + void Init(uint32_t baudRate) + { + } + + uint16_t numChars = 0; + uint8_t checksum = 0; + + // Send a character to the 3D printer. + // A typical command string is only about 12 characters long, which at 115200 baud takes just over 1ms to send. + // So there is no particular reason to use interrupts, and by so doing so we avoid having to handle buffer full situations. + void RawSendChar(char c) + { + HAL_UART_Transmit(&huart2, (uint8_t *)&c, 1, 1000); + // while(HAL_UART_Transmit(&huart2, &c, 1, 1000) != HAL_OK) { } + } + + void SendCharAndChecksum(char c) + { + checksum ^= c; + RawSendChar(c); + ++numChars; + } + + void SendChar(char c) + decrease(numChars == 0) + { + if (c == '\n') + { + if (numChars != 0) + { + // Send the checksum + RawSendChar('*'); + char digit0 = checksum % 10 + '0'; + checksum /= 10; + char digit1 = checksum % 10 + '0'; + checksum /= 10; + if (checksum != 0) + { + RawSendChar(checksum + '0'); + } + RawSendChar(digit1); + RawSendChar(digit0); + } + RawSendChar(c); + numChars = 0; + } + else + { + if (numChars == 0) + { + checksum = 0; + // Send a dummy line number + SendCharAndChecksum('N'); + SendInt(lineNumber++); // numChars is no longer zero, so only recurses once + SendCharAndChecksum(' '); + } + SendCharAndChecksum(c); + } + } + + void SendString(const char * array s) + { + while (*s != 0) + { + SendChar(*s++); + } + } + + void SendFilename(const char * array dir, const char * array name) + { + if (*dir != 0) + { + // We have a directory, so send it followed by '/' if necessary + char c; + while ((c = *dir) != 0) + { + SendChar(c); + ++dir; + } + if (c != '/') + { + SendChar('/'); + } + + } + SendString(name); + } + + void SendInt(int i) + decrease(i < 0; i) + { + if (i < 0) + { + SendChar('-'); + i = -i; + } + if (i >= 10) + { + SendInt(i/10); + i %= 10; + } + SendChar((char)((char)i + '0')); + } + + // Receive data processing + const size_t rxBufsize = 2048; + static volatile char rxBuffer[rxBufsize]; + static volatile size_t nextIn = 0; + static size_t nextOut = 0; + static bool inError = false; + + // Enumeration to represent the json parsing state. + // We don't allow nested objects or nested arrays, so we don't need a state stack. + // An additional variable elementCount is 0 if we are not in an array, else the number of elements we have found (including the current one) + enum JsonState + { + jsBegin, // initial state, expecting '{' + jsExpectId, // just had '{' so expecting a quoted ID + jsId, // expecting an identifier, or in the middle of one + jsHadId, // had a quoted identifier, expecting ':' + jsVal, // had ':', expecting value + jsStringVal, // had '"' and expecting or in a string value + jsStringEscape, // just had backslash in a string + jsIntVal, // receiving an integer value + jsNegIntVal, // had '-' so expecting a integer value + jsFracVal, // receiving a fractional value + jsEndVal, // had the end of a string or array value, expecting comma or ] or } + jsError // something went wrong + }; + + JsonState state = jsBegin; + + String<20> fieldId; + String<100> fieldVal; + int arrayElems = -1; + + static void ProcessField() + { + ProcessReceivedValue(fieldId.c_str(), fieldVal.c_str(), arrayElems); + fieldVal.clear(); + } + + static void EndArray() + { + ProcessArrayLength(fieldId.c_str(), arrayElems); + arrayElems = -1; + } + + // Look for combining characters in the string value and convert them if possible + static void ConvertUnicode() + { + unsigned int numContinuationBytesLeft = 0; + uint32_t charVal; + for (size_t i = 0; i < fieldVal.size(); ) + { + const unsigned char c = fieldVal[i++]; + if (numContinuationBytesLeft == 0) + { + if (c >= 0x80) + { + if ((c & 0xE0) == 0xC0) + { + charVal = (uint32_t)(c & 0x1F); + numContinuationBytesLeft = 1; + } + else if ((c & 0xF0) == 0xE0) + { + charVal = (uint32_t)(c & 0x0F); + numContinuationBytesLeft = 2; + } + else if ((c & 0xF8) == 0xF0) + { + charVal = (uint32_t)(c & 0x07); + numContinuationBytesLeft = 3; + } + else if ((c & 0xFC) == 0xF8) + { + charVal = (uint32_t)(c & 0x03); + numContinuationBytesLeft = 4; + } + else if ((c & 0xFE) == 0xFC) + { + charVal = (uint32_t)(c & 0x01); + numContinuationBytesLeft = 5; + } + } + } + else if ((c & 0xC0) == 0x80) + { + charVal = (charVal << 6) | (c & 0x3F); + --numContinuationBytesLeft; + if (numContinuationBytesLeft == 0) + { + const char* array trtab; + switch(charVal) + { + case 0x0300: // grave accent + trtab = trGrave; + break; + case 0x0301: // acute accent + trtab = trAcute; + break; + case 0x0302: // circumflex + trtab = trCircumflex; + break; + case 0x0303: // tilde + trtab = trTilde; + break; + case 0x0308: // umlaut + trtab = trUmlaut; + break; + case 0x030A: // small circle + trtab = trCircle; + break; + case 0x327: // cedilla + trtab = trCedilla; + break; + default: + trtab = nullptr; + break; + } + + // If it is a diacritical mark that we handle, try to combine it with the previous character. + // The diacritical marks are in the range 03xx so they are encoded as 2 UTF8 bytes. + if (trtab != nullptr && i > 2) + { + const char c2 = fieldVal[i - 3]; + while (*trtab != 0 && *trtab != c2) + { + trtab += 2; + } + if (*trtab != 0) + { + // Get he translated character and encode it as 2 ITF8 bytes + const unsigned char c3 = trtab[1]; + fieldVal[i - 3] = (c3 >> 6) | 0xC0; + fieldVal[i - 2] = (c3 & 0x3F) | 0x80; + fieldVal.erase(i - 1); + --i; + } + } + } + } + else + { + // Bad UTF8 state + numContinuationBytesLeft = 0; + } + } + } + + void CheckInput() + { + while (nextIn != nextOut) + { + char c = rxBuffer[nextOut]; + nextOut = (nextOut + 1) % rxBufsize; + if (c == '\n') + { + state = jsBegin; // abandon current parse (if any) and start again + } + else + { + switch(state) + { + case jsBegin: // initial state, expecting '{' + if (c == '{') + { + StartReceivedMessage(); + state = jsExpectId; + fieldVal.clear(); + } + break; + + case jsExpectId: // expecting a quoted ID + switch (c) + { + case ' ': + break; + case '"': + fieldId.clear(); + state = jsId; + break; + case '}': + EndReceivedMessage(); + state = jsBegin; + break; + default: + state = jsError; + break; + } + break; + + case jsId: // expecting an identifier, or in the middle of one + switch (c) + { + case '"': + state = jsHadId; + break; + default: + if (c >= ' ' && !fieldId.full()) + { + fieldId.add(c); + } + else + { + state = jsError; + } + break; + } + break; + + case jsHadId: // had a quoted identifier, expecting ':' + switch(c) + { + case ':': + arrayElems = -1; + state = jsVal; + break; + case ' ': + break; + default: + state = jsError; + break; + } + break; + + case jsVal: // had ':' or ':[', expecting value + switch(c) + { + case ' ': + break; + case '"': + fieldVal.clear(); + state = jsStringVal; + break; + case '[': + if (arrayElems == -1) // if not already readuing an array + { + arrayElems = 0; // start an array + } + else + { + state = jsError; // we don't support nested arrays + } + break; + case ']': + if (arrayElems == 0) + { + EndArray(); // empty array + state = jsEndVal; + } + else + { + state = jsError; // ']' received without a matching '[' first + } + break; + case '-': + fieldVal.clear(); + fieldVal.add(c); + state = jsNegIntVal; + break; + default: + if (c >= '0' && c <= '9') + { + fieldVal.clear(); + fieldVal.add(c); + state = jsIntVal; + break; + } + else + { + state = jsError; + } + } + break; + + case jsStringVal: // just had '"' and expecting a string value + switch (c) + { + case '"': + ConvertUnicode(); + ProcessField(); + state = jsEndVal; + break; + case '\\': + state = jsStringEscape; + break; + default: + if (c < ' ') + { + state = jsError; + } + else if (!fieldVal.full()) + { + fieldVal.add(c); + } + break; + } + break; + + case jsStringEscape: // just had backslash in a string + if (!fieldVal.full()) + { + switch (c) + { + case '"': + case '\\': + case '/': + fieldVal.add(c); + break; + case 'n': + case 't': + fieldVal.add(' '); // replace newline and tab by space + break; + case 'b': + case 'f': + case 'r': + default: + break; + } + } + state = jsStringVal; + break; + + case jsNegIntVal: // had '-' so expecting a integer value + if (c >= '0' && c <= '9') + { + fieldVal.add(c); + state = jsIntVal; + } + else + { + state = jsError; + } + break; + + case jsIntVal: // receiving an integer value + switch(c) + { + case '.': + if (fieldVal.full()) + { + state = jsError; + } + else + { + fieldVal.add(c); + state = jsFracVal; + } + break; + case ',': + ProcessField(); + if (arrayElems >= 0) + { + ++arrayElems; + state = jsVal; + } + else + { + state = jsExpectId; + } + break; + case ']': + if (arrayElems >= 0) + { + ProcessField(); + ++arrayElems; + EndArray(); + state = jsEndVal; + } + else + { + state = jsError; + } + break; + case '}': + if (arrayElems == -1) + { + ProcessField(); + EndReceivedMessage(); + state = jsBegin; + } + else + { + state = jsError; + } + break; + default: + if (c >= '0' && c <= '9' && !fieldVal.full()) + { + fieldVal.add(c); + } + else + { + state = jsError; + } + break; + } + break; + + case jsFracVal: // receiving a fractional value + switch(c) + { + case ',': + ProcessField(); + if (arrayElems >= 0) + { + ++arrayElems; + state = jsVal; + } + else + { + state = jsExpectId; + } + break; + case ']': + if (arrayElems >= 0) + { + ProcessField(); + ++arrayElems; + EndArray(); + state = jsEndVal; + } + else + { + state = jsError; + } + break; + case '}': + if (arrayElems == -1) + { + ProcessField(); + EndReceivedMessage(); + state = jsBegin; + } + else + { + state = jsError; + } + break; + default: + if (c >= '0' && c <= '9' && !fieldVal.full()) + { + fieldVal.add(c); + } + else + { + state = jsError; + } + break; + } + break; + + case jsEndVal: // had the end of a string or array value, expecting comma or ] or } + switch (c) + { + case ',': + if (arrayElems >= 0) + { + ++arrayElems; + fieldVal.clear(); + state = jsVal; + } + else + { + state = jsExpectId; + } + break; + case ']': + if (arrayElems >= 0) + { + ++arrayElems; + EndArray(); + state = jsEndVal; + } + else + { + state = jsError; + } + break; + case '}': + if (arrayElems == -1) + { + EndReceivedMessage(); + state = jsBegin; + } + else + { + state = jsError; + } + break; + default: + break; + } + break; + + case jsError: + // Ignore all characters. State will be reset to jsBegin at the start of this function when we receive a newline. + break; + } + } + } + } + + // Called by the ISR to store a received character. + // If the buffer is full, we wait for the next end-of-line. + void receiveChar(char c) + { + if (c == '\n') + { + inError = false; + } + if (!inError) + { + size_t temp = (nextIn + 1) % rxBufsize; + if (temp == nextOut) + { + inError = true; + } + else + { + rxBuffer[nextIn] = c; + nextIn = temp; + } + } + } + + // Called by the ISR to signify an error. We wait for the next end of line. + void receiveError() + { + inError = true; + } +} + +extern "C" { + + extern uint8_t comm1RxBuffer; + + void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) + { + if (huart->Instance == USART2) + { + __HAL_UART_FLUSH_DRREGISTER(&huart2); // Clear the buffer to prevent overrun + SerialIo::receiveChar(comm1RxBuffer); + } + } + +}; + +// End diff --git a/Src/UTouch.cpp b/Src/UTouch.cpp new file mode 100644 index 0000000..f3470dc --- /dev/null +++ b/Src/UTouch.cpp @@ -0,0 +1,170 @@ +/* + UTouch.cpp - library support for Color TFT LCD Touch screens on SAM3X + Originally based on Utouch library by Henning Karlsen. + Rewritten by D Crocker using the approach described in TI app note http://www.ti.com/lit/pdf/sbaa036. +*/ + +#include "stm32f1xx_hal.h" +#include "cmsis_os.h" + +#include "UTouch.h" + +extern SPI_HandleTypeDef hspi3; + +void UTouch::init(uint16_t xp, uint16_t yp, DisplayOrientation orientationAdjust) +{ + orientAdjust = orientationAdjust; + disp_x_size = xp; + disp_y_size = yp; + offsetX = 0; + scaleX = (uint16_t)(((uint32_t)(disp_x_size - 1) << 16)/4095); + offsetY = 0; + scaleY = (uint16_t)(((uint32_t)(disp_y_size - 1) << 16)/4095); + + HAL_GPIO_WritePin(TOUCH_nCS_GPIO_Port, TOUCH_nCS_Pin, GPIO_PIN_RESET); + + uint8_t pTxData[3] = { 0xd4, 0, 0 }; + uint8_t pRxData[3]; + + /* warmup */ + HAL_SPI_TransmitReceive(&hspi3, pTxData, pRxData, 3, 1000); + pTxData[0] = 0x94; + HAL_SPI_TransmitReceive(&hspi3, pTxData, pRxData, 3, 1000); + + HAL_GPIO_WritePin(TOUCH_nCS_GPIO_Port, TOUCH_nCS_Pin, GPIO_PIN_SET); +} + +// If the panel is touched, return the coordinates in x and y and return true; else return false +bool UTouch::read(uint16_t &px, uint16_t &py, uint16_t * null rawX, uint16_t * null rawY) +{ + bool ret = false; + if (HAL_GPIO_ReadPin(TOUCH_DI_GPIO_Port, TOUCH_DI_Pin) == GPIO_PIN_RESET) // if screen is touched + { + HAL_GPIO_WritePin(TOUCH_nCS_GPIO_Port, TOUCH_nCS_Pin, GPIO_PIN_RESET); + + osDelay(1); // allow the screen to settle + uint16_t tx; + + if (getTouchData(false, tx)) + { + uint16_t ty; + + if (getTouchData(true, ty)) + { + if (HAL_GPIO_ReadPin(TOUCH_DI_GPIO_Port, TOUCH_DI_Pin) == GPIO_PIN_RESET) + { + int16_t valx = (orientAdjust & SwapXY) ? ty : tx; + if (orientAdjust & ReverseX) + { + valx = 4095 - valx; + } + + int16_t cx = (int16_t)(((uint32_t)valx * (uint32_t)scaleX) >> 16) - offsetX; + px = (cx < 0) ? 0 : (cx >= disp_x_size) ? disp_x_size - 1 : (uint16_t)cx; + + int16_t valy = (orientAdjust & SwapXY) ? tx : ty; + if (orientAdjust & ReverseY) + { + valy = 4095 - valy; + } + + int16_t cy = (int16_t)(((uint32_t)valy * (uint32_t)scaleY) >> 16) - offsetY; + py = (cy < 0) ? 0 : (cy >= disp_y_size) ? disp_y_size - 1 : (uint16_t)cy; + if (rawX != nullptr) + { + *rawX = valx; + } + if (rawY != nullptr) + { + *rawY = valy; + } + ret = true; + } + } + } + + HAL_GPIO_WritePin(TOUCH_nCS_GPIO_Port, TOUCH_nCS_Pin, GPIO_PIN_SET); + } + return ret; +} + +// Get data from the touch chip. CS has already been set low. +// We need to allow the touch chip ADC input to settle. See TI app note http://www.ti.com/lit/pdf/sbaa036. +bool UTouch::getTouchData(bool wantY, uint16_t &rslt) +{ + uint8_t pTxData[3] = { (uint8_t) ((wantY) ? 0xD3 : 0x93), 0, 0 }; + uint8_t pRxData[3]; + + /* warmup */ + HAL_SPI_Transmit(&hspi3, pTxData, 3, 1000); + + const size_t numReadings = 8; + const uint16_t maxDiff = 40; // needs to be big enough to handle jitter. + // 8 was OK for the 4.3 and 5 inch displays but not the 7 inch. + // 25 is OK for most 7" displays. + const unsigned int maxAttempts = 16; + + uint16_t ring[numReadings]; + uint32_t sum = 0; + + // Take enough readings to fill the ring buffer + for (size_t i = 0; i < numReadings; ++i) + { + HAL_SPI_TransmitReceive(&hspi3, pTxData, pRxData, 3, 1000); + uint16_t val = *((uint16_t *) (pRxData + 1)); + + ring[i] = val; + sum += val; + } + + // Test whether every reading is within 'maxDiff' of the average reading. + // If it is, return the average reading. + // If not, take another reading and try again, up to 'maxAttempts' times. + uint16_t avg; + size_t last = 0; + bool ok; + for (unsigned int i = 0; i < maxAttempts; ++i) + { + avg = (uint16_t)(sum/numReadings); + ok = true; + for (size_t i = 0; ok && i < numReadings; ++i) + { + if (diff(avg, ring[i]) > maxDiff) + { + ok = false; + break; + } + } + if (ok) + { + break; + } + + // Take another reading + sum -= ring[last]; + + HAL_SPI_TransmitReceive(&hspi3, pTxData, pRxData, 3, 1000); + uint16_t val = *((uint16_t *) (pRxData + 1)); + + ring[last] = val; + sum += val; + last = (last + 1) % numReadings; + } + + pTxData[0] &= 0xF8; + HAL_SPI_Transmit(&hspi3, pTxData, 3, 1000); + pTxData[0] = 0; + HAL_SPI_Transmit(&hspi3, pTxData, 3, 1000); // read the final data + rslt = avg; + return ok; +} + +void UTouch::calibrate(uint16_t xlow, uint16_t xhigh, uint16_t ylow, uint16_t yhigh, uint16_t margin) +{ + scaleX = (uint16_t)(((uint32_t)(disp_x_size - 1 - 2 * margin) << 16)/(xhigh - xlow)); + offsetX = (int16_t)(((uint32_t)xlow * (uint32_t)scaleX) >> 16) - (int16_t)margin; + scaleY = (uint16_t)(((uint32_t)(disp_y_size - 1 - 2 * margin) << 16)/(yhigh - ylow)); + offsetY = (int16_t)(((uint32_t)ylow * (uint32_t)scaleY) >> 16) - (int16_t)margin; +} + +// End diff --git a/Src/UserInterface.cpp b/Src/UserInterface.cpp new file mode 100644 index 0000000..1c98f2a --- /dev/null +++ b/Src/UserInterface.cpp @@ -0,0 +1,2031 @@ +/* + * UserInterface.cpp + * + * Created on: 7 Jan 2017 + * Author: David + */ + +#ifndef OEM_LAYOUT + +#include "UserInterface.h" +#include "PanelDue.h" +#include "FileManager.h" +#include "MessageLog.h" +#include "Misc.h" +#include "Vector.h" +#include "Icons.h" +#include "Buzzer.h" +#include "Reset.h" +#include "SerialIo.h" +#include "Strings.h" + +const unsigned int numLanguages = 4; +static_assert(ARRAY_SIZE(LanguageTables) == numLanguages, "Wrong number of languages in LanguageTable"); +static const char* array const axisNames[] = { "X", "Y", "Z", "U", "V", "W" }; + +const Icon heaterIcons[maxHeaters] = { IconBed, IconNozzle1, IconNozzle2, IconNozzle3, IconNozzle4 }; + +// Public fields +TextField *fwVersionField, *userCommandField; +IntegerField *freeMem, *touchX, *touchY; +TextButton *filenameButtons[numDisplayedFiles]; +StaticTextField *touchCalibInstruction, *debugField; +StaticTextField *messageTextFields[numMessageRows], *messageTimeFields[numMessageRows]; + +// Private fields +static PopupWindow *setTempPopup, *movePopup, *extrudePopup, *fileListPopup, *filePopup, *baudPopup, *volumePopup, *areYouSurePopup, *keyboardPopup, *languagePopup, *coloursPopup; +static SingleButton *scrollFilesLeftButton, *scrollFilesRightButton, *filesUpButton, *changeCardButton; +static StaticTextField *areYouSureTextField, *areYouSureQueryField, *macroPopupTitleField; +static DisplayField *baseRoot, *commonRoot, *controlRoot, *printRoot, *messageRoot, *setupRoot; +static SingleButton *homeButtons[MAX_AXES], *toolButtons[maxHeaters], *homeAllButton, *bedCompButton; +static FloatField *axisPos[MAX_AXES]; +static FloatField *currentTemps[maxHeaters]; +static FloatField *fpHeightField, *fpLayerHeightField, *babystepOffsetField; +static IntegerField *fpSizeField, *fpFilamentField, *fileListErrorField, *filePopupTitleField; +static ProgressBar *printProgressBar; +static SingleButton *tabControl, *tabPrint, *tabMsg, *tabSetup; +static ButtonBase *filesButton, *pauseButton, *resumeButton, *resetButton, *babystepButton; +static TextField *timeLeftField, *zProbe; +static TextField *fpNameField, *fpGeneratedByField; +static StaticTextField *moveAxisRows[MAX_AXES]; +static StaticTextField *nameField, *statusField, *settingsNotSavedField; +static IntegerButton *activeTemps[maxHeaters], *standbyTemps[maxHeaters]; +static IntegerButton *spd, *extrusionFactors[maxHeaters - 1], *fanSpeed, *baudRateButton, *volumeButton; +static TextButton *languageButton, *coloursButton; +static SingleButton *moveButton, *extrudeButton, *macroButton; +static PopupWindow *alertPopup, *babystepPopup; + +static ButtonBase * null currentTab = nullptr; + +static ButtonPress currentButton; +static ButtonPress fieldBeingAdjusted; +static ButtonPress currentExtrudeRatePress, currentExtrudeAmountPress; + +const size_t machineNameLength = 30; +const size_t printingFileLength = 40; +const size_t zprobeBufLength = 12; +const size_t alertTextLength = 80; +const size_t generatedByTextLength = 50; + +static String machineName; +static String printingFile; +static String zprobeBuf; +static String alertText; +static String generatedByText; + +const size_t maxUserCommandLength = 40; // max length of a user gcode command +const size_t numUserCommandBuffers = 6; // number of command history buffers plus one + +static String userCommandBuffers[numUserCommandBuffers]; +static size_t currentUserCommandBuffer = 0, currentHistoryBuffer = 0; + +static int oldIntValue; +static bool restartNeeded = false; +int heaterStatus[maxHeaters]; +static Event eventToConfirm = evNull; + +const char* array null currentFile = nullptr; // file whose info is displayed in the file info popup +const StringTable * strings = &LanguageTables[0]; +static bool keyboardIsDisplayed = false; + +// Create a standard popup window with a title and a close button at the top right +PopupWindow *CreatePopupWindow(PixelNumber ph, PixelNumber pw, Colour pb, Colour pBorder, Colour textColour, Colour imageBackColour, const char * null title, PixelNumber topMargin = popupTopMargin) +{ + PopupWindow *window = new PopupWindow(ph, pw, pb, pBorder); + DisplayField::SetDefaultColours(textColour, pb); + if (title != nullptr) + { + window->AddField(new StaticTextField(topMargin + labelRowAdjust, popupSideMargin + closeButtonWidth + popupFieldSpacing, + pw - 2 * (popupSideMargin + closeButtonWidth + popupFieldSpacing), TextAlignment::Centre, title)); + } + DisplayField::SetDefaultColours(textColour, imageBackColour); + window->AddField(new IconButton(popupTopMargin, pw - (closeButtonWidth + popupSideMargin), closeButtonWidth, IconCancel, evCancel)); + return window; +} + +// Add a text button +TextButton *AddTextButton(PixelNumber row, unsigned int col, unsigned int numCols, const char* array text, Event evt, const char* param) +{ + PixelNumber width = (DISPLAY_X - 2 * margin + fieldSpacing)/numCols - fieldSpacing; + PixelNumber xpos = col * (width + fieldSpacing) + margin; + TextButton *f = new TextButton(row - 2, xpos, width, text, evt, param); + mgr.AddField(f); + return f; +} + +// Add an integer button +IntegerButton *AddIntegerButton(PixelNumber row, unsigned int col, unsigned int numCols, const char * array null label, const char * array null units, Event evt) +{ + PixelNumber width = (DISPLAY_X - 2 * margin + fieldSpacing)/numCols - fieldSpacing; + PixelNumber xpos = col * (width + fieldSpacing) + margin; + IntegerButton *f = new IntegerButton(row - 2, xpos, width, label, units); + f->SetEvent(evt, 0); + mgr.AddField(f); + return f; +} + +// Add an icon button with a string parameter +IconButton *AddIconButton(PixelNumber row, unsigned int col, unsigned int numCols, Icon icon, Event evt, const char* param) +{ + PixelNumber width = (DISPLAY_X - 2 * margin + fieldSpacing)/numCols - fieldSpacing; + PixelNumber xpos = col * (width + fieldSpacing) + margin; + IconButton *f = new IconButton(row - 2, xpos, width, icon, evt, param); + mgr.AddField(f); + return f; +} + +// Create a row of text buttons. +// Optionally, set one to 'pressed' and return that one. +// Set the colours before calling this +ButtonPress CreateStringButtonRow(Window * pf, PixelNumber top, PixelNumber left, PixelNumber totalWidth, PixelNumber spacing, unsigned int numButtons, + const char* array const text[], const char* array const params[], Event evt, int selected = -1) +{ + const PixelNumber step = (totalWidth + spacing)/numButtons; + ButtonPress bp; + for (unsigned int i = 0; i < numButtons; ++i) + { + TextButton *tp = new TextButton(top, left + i * step, step - spacing, text[i], evt, params[i]); + pf->AddField(tp); + if ((int)i == selected) + { + tp->Press(true, 0); + bp = ButtonPress(tp, 0); + } + } + return bp; +} + +// Create a row of icon buttons. +// Set the colours before calling this +void CreateIconButtonRow(Window * pf, PixelNumber top, PixelNumber left, PixelNumber totalWidth, PixelNumber spacing, unsigned int numButtons, + const Icon icons[], const char* array const params[], Event evt) +{ + const PixelNumber step = (totalWidth + spacing)/numButtons; + for (unsigned int i = 0; i < numButtons; ++i) + { + pf->AddField(new IconButton(top, left + i * step, step - spacing, icons[i], evt, params[i])); + } +} + +// Create a popup bar with string parameters +PopupWindow *CreateStringPopupBar(const ColourScheme& colours, PixelNumber width, unsigned int numEntries, const char* const text[], const char* const params[], Event ev) +{ + PopupWindow *pf = new PopupWindow(popupBarHeight, width, colours.popupBackColour, colours.popupBorderColour); + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.popupButtonBackColour); + PixelNumber step = (width - 2 * popupSideMargin + popupFieldSpacing)/numEntries; + for (unsigned int i = 0; i < numEntries; ++i) + { + pf->AddField(new TextButton(popupTopMargin, popupSideMargin + i * step, step - popupFieldSpacing, text[i], ev, params[i])); + } + return pf; +} + +// Create a popup bar with integer parameters +// If the 'params' parameter is null then we use 0, 1, 2.. at the parameters +PopupWindow *CreateIntPopupBar(const ColourScheme& colours, PixelNumber width, unsigned int numEntries, const char* const text[], const int * null params, Event ev, Event zeroEv) +{ + PopupWindow *pf = new PopupWindow(popupBarHeight, width, colours.popupBackColour, colours.popupBorderColour); + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.popupButtonBackColour); + PixelNumber step = (width - 2 * popupSideMargin + popupFieldSpacing)/numEntries; + for (unsigned int i = 0; i < numEntries; ++i) + { + const int iParam = (params == nullptr) ? (int)i : params[i]; + pf->AddField(new TextButton(popupSideMargin, popupSideMargin + i * step, step - popupFieldSpacing, text[i], (params[i] == 0) ? zeroEv : ev, iParam)); + } + return pf; +} + +// Nasty hack to work around bug in RepRapFirmware 1.09k and earlier +// The M23 and M30 commands don't work if we send the full path, because "0:/gcodes/" gets prepended regardless. +const char * array StripPrefix(const char * array dir) +{ + if ((GetFirmwareFeatures() && noGcodesFolder) == 0) // if running RepRapFirmware + { + const size_t len = strlen(dir); + if (len >= 8 && memcmp(dir, "/gcodes/", 8) == 0) + { + dir += 8; + } + else if (len >= 10 && memcmp(dir, "0:/gcodes/", 10) == 0) + { + dir += 10; + } + else if (strcmp(dir, "/gcodes") == 0 || strcmp(dir, "0:/gcodes") == 0) + { + dir += len; + } + } + return dir; +} + +// Adjust the brightness +void ChangeBrightness(bool up) +{ + int adjust = max(1, GetBrightness()/16); + if (!up) + { + adjust = -adjust; + } + SetBrightness(GetBrightness() + adjust); +} + +// Update an integer field, provided it isn't the one being adjusted +// Don't update it if the value hasn't changed, because that makes the display flicker unnecessarily +void UpdateField(IntegerButton *f, int val) +{ + if (f != fieldBeingAdjusted.GetButton() && f->GetValue() != val) + { + f->SetValue(val); + } +} + +void PopupAreYouSure(Event ev, const char* text, const char* query = strings->areYouSure) +{ + eventToConfirm = ev; + areYouSureTextField->SetValue(text); + areYouSureQueryField->SetValue(query); + mgr.SetPopup(areYouSurePopup, AutoPlace, AutoPlace); +} + +void PopupRestart() +{ + PopupAreYouSure(evRestart, "Restart required", "Restart now?"); +} + +void CreateIntegerAdjustPopup(const ColourScheme& colours) +{ + // Create the popup window used to adjust temperatures, fan speed, extrusion factor etc. + static const char* const tempPopupText[] = {"-5", "-1", "Set", "+1", "+5"}; + static const int tempPopupParams[] = { -5, -1, 0, 1, 5 }; + setTempPopup = CreateIntPopupBar(colours, tempPopupBarWidth, 5, tempPopupText, tempPopupParams, evAdjustInt, evSetInt); +} + +// Create the movement popup window +void CreateMovePopup(const ColourScheme& colours) +{ + static const char * array const xyJogValues[] = { "-100", "-10", "-1", "-0.1", "0.1", "1", "10", "100" }; + static const char * array const zJogValues[] = { "-50", "-5", "-0.5", "-0.05", "0.05", "0.5", "5", "50" }; + + movePopup = CreatePopupWindow(movePopupHeight, movePopupWidth, colours.popupBackColour, colours.popupBorderColour, colours.popupTextColour, colours.buttonImageBackColour, strings->moveHead); + PixelNumber ypos = popupTopMargin + buttonHeight + moveButtonRowSpacing; + const PixelNumber xpos = popupSideMargin + axisLabelWidth; + Event e = evMoveX; + for (size_t i = 0; i < MAX_AXES; ++i) + { + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.popupButtonBackColour); + const char * array const * array values = (axisNames[i][0] == 'Z') ? zJogValues : xyJogValues; + CreateStringButtonRow(movePopup, ypos, xpos, movePopupWidth - xpos - popupSideMargin, fieldSpacing, 8, values, values, e); + + // We create the label after the button row, so that the buttons follow it in the field order, which makes it easier to hide them + DisplayField::SetDefaultColours(colours.popupTextColour, colours.popupBackColour); + StaticTextField * const tf = new StaticTextField(ypos + labelRowAdjust, popupSideMargin, axisLabelWidth, TextAlignment::Left, axisNames[i]); + movePopup->AddField(tf); + moveAxisRows[i] = tf; + UI::ShowAxis(i, i < MIN_AXES); + + ypos += buttonHeight + moveButtonRowSpacing; + e = (Event)((uint8_t)e + 1); + } +} + +// Create the extrusion controls popup +void CreateExtrudePopup(const ColourScheme& colours) +{ + static const char * array extrudeAmountValues[] = { "100", "50", "20", "10", "5", "1" }; + static const char * array extrudeSpeedValues[] = { "50", "40", "20", "10", "5" }; + static const char * array extrudeSpeedParams[] = { "3000", "2400", "1200", "600", "300" }; + + extrudePopup = CreatePopupWindow(extrudePopupHeight, extrudePopupWidth, colours.popupBackColour, colours.popupBorderColour, colours.popupButtonTextColour, colours.buttonImageBackColour, strings->extrusionAmount); + PixelNumber ypos = popupTopMargin + buttonHeight + extrudeButtonRowSpacing; + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.popupButtonBackColour); + currentExtrudeAmountPress = CreateStringButtonRow(extrudePopup, ypos, popupSideMargin, extrudePopupWidth - 2 * popupSideMargin, fieldSpacing, 6, extrudeAmountValues, extrudeAmountValues, evExtrudeAmount, 3); + ypos += buttonHeight + extrudeButtonRowSpacing; + DisplayField::SetDefaultColours(colours.popupTextColour, colours.popupBackColour); + extrudePopup->AddField(new StaticTextField(ypos + labelRowAdjust, popupSideMargin, extrudePopupWidth - 2 * popupSideMargin, TextAlignment::Centre, strings->extrusionSpeed)); + ypos += buttonHeight + extrudeButtonRowSpacing; + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.popupButtonBackColour); + currentExtrudeRatePress = CreateStringButtonRow(extrudePopup, ypos, popupSideMargin, extrudePopupWidth - 2 * popupSideMargin, fieldSpacing, 5, extrudeSpeedValues, extrudeSpeedParams, evExtrudeRate, 4); + ypos += buttonHeight + extrudeButtonRowSpacing; + extrudePopup->AddField(new TextButton(ypos, popupSideMargin, extrudePopupWidth/3 - 2 * popupSideMargin, strings->extrude, evExtrude)); + extrudePopup->AddField(new TextButton(ypos, (2 * extrudePopupWidth)/3 + popupSideMargin, extrudePopupWidth/3 - 2 * popupSideMargin, strings->retract, evRetract)); +} + +// Create the popup used to list files and macros +void CreateFileListPopup(const ColourScheme& colours) +{ + fileListPopup = CreatePopupWindow(fileListPopupHeight, fileListPopupWidth, colours.popupBackColour, colours.popupBorderColour, colours.popupTextColour, colours.buttonImageBackColour, nullptr); + const PixelNumber closeButtonPos = fileListPopupWidth - closeButtonWidth - popupSideMargin; + const PixelNumber navButtonWidth = (closeButtonPos - popupSideMargin)/7; + const PixelNumber upButtonPos = closeButtonPos - navButtonWidth - fieldSpacing; + const PixelNumber rightButtonPos = upButtonPos - navButtonWidth - fieldSpacing; + const PixelNumber leftButtonPos = rightButtonPos - navButtonWidth - fieldSpacing; + const PixelNumber textPos = popupSideMargin + navButtonWidth; + const PixelNumber changeButtonPos = popupSideMargin; + + DisplayField::SetDefaultColours(colours.popupTextColour, colours.popupBackColour); + fileListPopup->AddField(filePopupTitleField = new IntegerField(popupTopMargin + labelRowAdjust, textPos, leftButtonPos - textPos, TextAlignment::Centre, strings->filesOnCard, nullptr)); + fileListPopup->AddField(macroPopupTitleField = new StaticTextField(popupTopMargin + labelRowAdjust, textPos, leftButtonPos - textPos, TextAlignment::Centre, strings->macros)); + + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.buttonImageBackColour); + fileListPopup->AddField(changeCardButton = new IconButton(popupTopMargin, changeButtonPos, navButtonWidth, IconFiles, evChangeCard, 0)); + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.popupButtonBackColour); + fileListPopup->AddField(scrollFilesLeftButton = new TextButton(popupTopMargin, leftButtonPos, navButtonWidth, "<", evScrollFiles, -1)); + scrollFilesLeftButton->Show(false); + fileListPopup->AddField(scrollFilesRightButton = new TextButton(popupTopMargin, rightButtonPos, navButtonWidth, ">", evScrollFiles, 1)); + scrollFilesRightButton->Show(false); + fileListPopup->AddField(filesUpButton = new IconButton(popupTopMargin, upButtonPos, navButtonWidth, IconUp, evNull)); + filesUpButton->Show(false); + + const PixelNumber fileFieldWidth = (fileListPopupWidth + fieldSpacing - (2 * popupSideMargin))/numFileColumns; + unsigned int fileNum = 0; + for (unsigned int c = 0; c < numFileColumns; ++c) + { + PixelNumber row = popupTopMargin; + for (unsigned int r = 0; r < numFileRows; ++r) + { + row += buttonHeight + fileButtonRowSpacing; + TextButton *t = new TextButton(row, (fileFieldWidth * c) + popupSideMargin, fileFieldWidth - fieldSpacing, nullptr, evNull); + t->Show(false); + fileListPopup->AddField(t); + filenameButtons[fileNum] = t; + ++fileNum; + } + } + + fileListErrorField = new IntegerField(popupTopMargin + 2 * (buttonHeight + fileButtonRowSpacing), popupSideMargin, fileListPopupWidth - (2 * popupSideMargin), + TextAlignment::Centre, strings->error, strings->accessingSdCard); + fileListErrorField->Show(false); + fileListPopup->AddField(fileListErrorField); +} + +// Create the popup window used to display the file dialog +void CreateFileActionPopup(const ColourScheme& colours) +{ + filePopup = CreatePopupWindow(fileInfoPopupHeight, fileInfoPopupWidth, colours.popupBackColour, colours.popupBorderColour, colours.popupTextColour, colours.buttonImageBackColour, "File information"); + DisplayField::SetDefaultColours(colours.popupTextColour, colours.popupBackColour); + PixelNumber ypos = popupTopMargin + (3 * rowTextHeight)/2; + fpNameField = new TextField(ypos, popupSideMargin, fileInfoPopupWidth - 2 * popupSideMargin, TextAlignment::Left, strings->fileName); + ypos += rowTextHeight; + fpSizeField = new IntegerField(ypos, popupSideMargin, fileInfoPopupWidth - 2 * popupSideMargin, TextAlignment::Left, strings->fileSize, " b"); + ypos += rowTextHeight; + fpLayerHeightField = new FloatField(ypos, popupSideMargin, fileInfoPopupWidth - 2 * popupSideMargin, TextAlignment::Left, 2, strings->layerHeight, "mm"); + ypos += rowTextHeight; + fpHeightField = new FloatField(ypos, popupSideMargin, fileInfoPopupWidth - 2 * popupSideMargin, TextAlignment::Left, 1, strings->objectHeight, "mm"); + ypos += rowTextHeight; + fpFilamentField = new IntegerField(ypos, popupSideMargin, fileInfoPopupWidth - 2 * popupSideMargin, TextAlignment::Left, strings->filamentNeeded, "mm"); + ypos += rowTextHeight; + fpGeneratedByField = new TextField(ypos, popupSideMargin, fileInfoPopupWidth - 2 * popupSideMargin, TextAlignment::Left, strings->generatedBy, generatedByText.c_str()); + filePopup->AddField(fpNameField); + filePopup->AddField(fpSizeField); + filePopup->AddField(fpLayerHeightField); + filePopup->AddField(fpHeightField); + filePopup->AddField(fpFilamentField); + filePopup->AddField(fpGeneratedByField); + + // Add the buttons + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.popupButtonBackColour); + filePopup->AddField(new TextButton(popupTopMargin + 8 * rowTextHeight, popupSideMargin, fileInfoPopupWidth/3 - 2 * popupSideMargin, strings->print, evPrint)); + filePopup->AddField(new IconButton(popupTopMargin + 8 * rowTextHeight, (2 * fileInfoPopupWidth)/3 + popupSideMargin, fileInfoPopupWidth/3 - 2 * popupSideMargin, IconTrash, evDeleteFile)); +} + +// Create the "Are you sure?" popup +void CreateAreYouSurePopup(const ColourScheme& colours) +{ + areYouSurePopup = new PopupWindow(areYouSurePopupHeight, areYouSurePopupWidth, colours.popupBackColour, colours.popupBorderColour); + DisplayField::SetDefaultColours(colours.popupTextColour, colours.popupBackColour); + areYouSurePopup->AddField(areYouSureTextField = new StaticTextField(popupSideMargin, margin, areYouSurePopupWidth - 2 * margin, TextAlignment::Centre, nullptr)); + areYouSurePopup->AddField(areYouSureQueryField = new StaticTextField(popupTopMargin + rowHeight, margin, areYouSurePopupWidth - 2 * margin, TextAlignment::Centre, nullptr)); + + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.popupButtonBackColour); + areYouSurePopup->AddField(new IconButton(popupTopMargin + 2 * rowHeight, popupSideMargin, areYouSurePopupWidth/2 - 2 * popupSideMargin, IconOk, evYes)); + areYouSurePopup->AddField(new IconButton(popupTopMargin + 2 * rowHeight, areYouSurePopupWidth/2 + 10, areYouSurePopupWidth/2 - 2 * popupSideMargin, IconCancel, evCancel)); +} + +// Create the baud rate adjustment popup +void CreateBaudRatePopup(const ColourScheme& colours) +{ + static const char* const baudPopupText[] = { "9600", "19200", "38400", "57600", "115200" }; + static const int baudPopupParams[] = { 9600, 19200, 38400, 57600, 115200 }; + baudPopup = CreateIntPopupBar(colours, fullPopupWidth, 5, baudPopupText, baudPopupParams, evAdjustBaudRate, evAdjustBaudRate); +} + +// Create the volume adjustment popup +void CreateVolumePopup(const ColourScheme& colours) +{ + static_assert(Buzzer::MaxVolume == 5, "MaxVolume assumed to be 5 here"); + static const char* const volumePopupText[Buzzer::MaxVolume + 1] = { "0", "1", "2", "3", "4", "5" }; + volumePopup = CreateIntPopupBar(colours, fullPopupWidth, Buzzer::MaxVolume + 1, volumePopupText, nullptr, evAdjustVolume, evAdjustVolume); +} + +// Create the colour scheme change popup +void CreateColoursPopup(const ColourScheme& colours) +{ + if (NumColourSchemes >= 2) + { + // Put all the colour scheme names in a single array for the call to CreateIntPopupBar + const char* coloursPopupText[NumColourSchemes]; + for (size_t i = 0; i < NumColourSchemes; ++i) + { + coloursPopupText[i] = strings->colourSchemeNames[i]; + } + coloursPopup = CreateIntPopupBar(colours, fullPopupWidth, NumColourSchemes, coloursPopupText, nullptr, evAdjustColours, evAdjustColours); + } + else + { + coloursPopup = nullptr; + } +} + +// Create the language popup (currently only affects the keyboard layout) +void CreateLanguagePopup(const ColourScheme& colours) +{ + languagePopup = new PopupWindow(popupBarHeight, fullPopupWidth, colours.popupBackColour, colours.popupBorderColour); + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.popupButtonBackColour); + PixelNumber step = (fullPopupWidth - 2 * popupSideMargin + popupFieldSpacing)/numLanguages; + for (unsigned int i = 0; i < numLanguages; ++i) + { + languagePopup->AddField(new TextButton(popupSideMargin, popupSideMargin + i * step, step - popupFieldSpacing, LanguageTables[i].languageName, evAdjustLanguage, i)); + } +} + +// Create the pop-up keyboard +void CreateKeyboardPopup(uint32_t language, ColourScheme colours) +{ + static const char* array const keysEN[4] = { "1234567890-+", "QWERTYUIOP", "ASDFGHJKL:", "ZXCVBNM./" }; + static const char* array const keysDE[4] = { "1234567890-+", "QWERTZUIOP", "ASDFGHJKL:", "YXCVBNM./" }; + static const char* array const keysFR[4] = { "1234567890-+", "AZERTWUIOP", "QSDFGHJKLM", "YXCVBN.:/" }; + static const char* array const * const keyboards[numLanguages] = { keysEN, keysDE, keysFR, keysEN /*, keysEN */ }; // Spain keyboard layout is same as English + + keyboardPopup = CreatePopupWindow(keyboardPopupHeight, keyboardPopupWidth, colours.popupBackColour, colours.popupBorderColour, colours.popupInfoTextColour, colours.buttonImageBackColour, nullptr, keyboardTopMargin); + + // Add the text area in which the command is built + DisplayField::SetDefaultColours(colours.popupInfoTextColour, colours.popupInfoBackColour); // need a different background colour + userCommandField = new TextField(keyboardTopMargin + labelRowAdjust, popupSideMargin, keyboardPopupWidth - 2 * popupSideMargin - closeButtonWidth - popupFieldSpacing, TextAlignment::Left, nullptr, "_"); + userCommandField->SetLabel(userCommandBuffers[currentUserCommandBuffer].c_str()); // set up to display the current user command + keyboardPopup->AddField(userCommandField); + + if (language >= numLanguages) + { + language = 0; + } + const char* array const * array const keys = keyboards[language]; + PixelNumber row = keyboardTopMargin + keyButtonVStep; + for (size_t i = 0; i < 4; ++i) + { + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.popupButtonBackColour); + PixelNumber column = popupSideMargin + (i * keyButtonHStep)/3; + const char * s = keys[i]; + while (*s != 0) + { + keyboardPopup->AddField(new CharButton(row, column, keyButtonWidth, *s, evKey)); + ++s; + column += keyButtonHStep; + } + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.buttonImageBackColour); + switch (i) + { + case 1: + keyboardPopup->AddField(new IconButton(row, keyboardPopupWidth - popupSideMargin - 2 * keyButtonWidth, 2 * keyButtonWidth, IconBackspace, evBackspace)); + break; + + case 2: + keyboardPopup->AddField(new IconButton(row, keyboardPopupWidth - popupSideMargin - (3 * keyButtonWidth)/2, (3 * keyButtonWidth)/2, IconUp, evUp)); + break; + + case 3: + keyboardPopup->AddField(new IconButton(row, keyboardPopupWidth - popupSideMargin - (3 * keyButtonWidth)/2, (3 * keyButtonWidth)/2, IconDown, evDown)); + break; + + default: + break; + } + row += keyButtonVStep; + } + + // Add the space and enter keys + const PixelNumber keyButtonHSpace = keyButtonHStep - keyButtonWidth; + const PixelNumber wideKeyButtonWidth = (keyboardPopupWidth - 2 * popupSideMargin - 2 * keyButtonHSpace)/5; + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.popupButtonBackColour); + keyboardPopup->AddField(new TextButton(row, popupSideMargin + wideKeyButtonWidth + keyButtonHSpace, 2 * wideKeyButtonWidth, nullptr, evKey, (int)' ')); + DisplayField::SetDefaultColours(colours.popupButtonTextColour, colours.buttonImageBackColour); + keyboardPopup->AddField(new IconButton(row, popupSideMargin + 3 * wideKeyButtonWidth + 2 * keyButtonHSpace, wideKeyButtonWidth, IconEnter, evSendKeyboardCommand)); +} + +// Create the message popup window +void CreateMessagePopup(const ColourScheme& colours) +{ + alertPopup = CreatePopupWindow(alertPopupHeight, alertPopupWidth, colours.alertPopupBackColour, colours.popupBorderColour, colours.alertPopupTextColour, colours.buttonImageBackColour, + strings->message); + DisplayField::SetDefaultColours(colours.alertPopupTextColour, colours.alertPopupBackColour); + alertPopup->AddField(new StaticTextField(popupTopMargin + 2 * rowTextHeight, popupSideMargin, alertPopupWidth - 2 * popupSideMargin, TextAlignment::Centre, alertText.c_str())); +} + +// Create the babystep popup +void CreateBabystepPopup(const ColourScheme& colours) +{ + static const Icon babystepIcons[2] = {IconUp, IconDown }; + static const char * array const babystepCommands[2] = { "M290 S0.05", "M290 S-0.05" }; + babystepPopup = CreatePopupWindow(babystepPopupHeight, babystepPopupWidth, colours.popupBackColour, colours.popupBorderColour, colours.popupTextColour, colours.buttonImageBackColour, + strings->babyStepping); + PixelNumber ypos = popupTopMargin + babystepRowSpacing; + DisplayField::SetDefaultColours(colours.popupTextColour, colours.popupBackColour); + babystepPopup->AddField(babystepOffsetField = new FloatField(ypos, popupSideMargin, babystepPopupWidth - 2 * popupSideMargin, TextAlignment::Left, 3, strings->currentZoffset, "mm")); + ypos += babystepRowSpacing; + DisplayField::SetDefaultColours(colours.popupTextColour, colours.buttonImageBackColour); + CreateIconButtonRow(babystepPopup, ypos, popupSideMargin, babystepPopupWidth - 2 * popupSideMargin, fieldSpacing, 2, babystepIcons, babystepCommands, evBabyStepAmount); +} + +// Create the grid of heater icons and temperatures +void CreateTemperatureGrid(const ColourScheme& colours) +{ + // Add the emergency stop button + DisplayField::SetDefaultColours(colours.stopButtonTextColour, colours.stopButtonBackColour); + mgr.AddField(new TextButton(row2, margin, bedColumn - fieldSpacing - margin - 16, strings->stop, evEmergencyStop)); + + // Add the labels and the debug field + DisplayField::SetDefaultColours(colours.labelTextColour, colours.defaultBackColour); + mgr.AddField(debugField = new StaticTextField(row1 + labelRowAdjust, margin, bedColumn - fieldSpacing - margin, TextAlignment::Left, "debug")); + mgr.AddField(new StaticTextField(row3 + labelRowAdjust, margin, bedColumn - fieldSpacing - margin, TextAlignment::Right, strings->current)); + mgr.AddField(new StaticTextField(row4 + labelRowAdjust, margin, bedColumn - fieldSpacing - margin, TextAlignment::Right, strings->active)); + mgr.AddField(new StaticTextField(row5 + labelRowAdjust, margin, bedColumn - fieldSpacing - margin, TextAlignment::Right, strings->standby)); + + // Add the grid + for (unsigned int i = 0; i < maxHeaters; ++i) + { + PixelNumber column = ((tempButtonWidth + fieldSpacing) * i) + bedColumn; + + // Add the icon button + DisplayField::SetDefaultColours(colours.buttonTextColour, colours.buttonImageBackColour); + SingleButton *b = new IconButton(row2, column, tempButtonWidth, heaterIcons[i], evSelectHead, i); + toolButtons[i] = b; + mgr.AddField(b); + + // Add the current temperature field + DisplayField::SetDefaultColours(colours.infoTextColour, colours.defaultBackColour); + FloatField *f = new FloatField(row3 + labelRowAdjust, column, tempButtonWidth, TextAlignment::Centre, 1); + f->SetValue(0.0); + currentTemps[i] = f; + mgr.AddField(f); + + // Add the active temperature button + DisplayField::SetDefaultColours(colours.buttonTextColour, colours.buttonTextBackColour); + IntegerButton *ib = new IntegerButton(row4, column, tempButtonWidth); + ib->SetEvent(evAdjustActiveTemp, i); + ib->SetValue(0); + activeTemps[i] = ib; + mgr.AddField(ib); + + // Add the standby temperature button + ib = new IntegerButton(row5, column, tempButtonWidth); + ib->SetEvent(evAdjustStandbyTemp, i); + ib->SetValue(0); + standbyTemps[i] = ib; + mgr.AddField(ib); + } +} + +// Create the extra fields for the Control tab +void CreateControlTabFields(const ColourScheme& colours) +{ + mgr.SetRoot(commonRoot); + + DisplayField::SetDefaultColours(colours.infoTextColour, colours.infoBackColour); + PixelNumber column = margin; + PixelNumber xyFieldWidth = (DISPLAY_X - (2 * margin) - (MAX_AXES * fieldSpacing))/(MAX_AXES + 1); + for (size_t i = 0; i < MAX_AXES; ++i) + { + FloatField *f = new FloatField(row6p3 + labelRowAdjust, column, xyFieldWidth, TextAlignment::Left, (i == 2) ? 2 : 1, axisNames[i]); + axisPos[i] = f; + f->SetValue(0.0); + mgr.AddField(f); + f->Show(i < MIN_AXES); + column += xyFieldWidth + fieldSpacing; + } + zprobeBuf[0] = 0; + mgr.AddField(zProbe = new TextField(row6p3 + labelRowAdjust, column, DISPLAY_X - column - margin, TextAlignment::Left, "P", zprobeBuf.c_str())); + + DisplayField::SetDefaultColours(colours.buttonTextColour, colours.notHomedButtonBackColour); + homeAllButton = AddIconButton(row7p7, 0, MAX_AXES + 2, IconHomeAll, evSendCommand, "G28"); + homeButtons[0] = AddIconButton(row7p7, 1, MAX_AXES + 2, IconHomeX, evSendCommand, "G28 X0"); + homeButtons[1] = AddIconButton(row7p7, 2, MAX_AXES + 2, IconHomeY, evSendCommand, "G28 Y0"); + homeButtons[2] = AddIconButton(row7p7, 3, MAX_AXES + 2, IconHomeZ, evSendCommand, "G28 Z0"); +#if MAX_AXES > 3 + homeButtons[3] = AddIconButton(row7p7, 4, MAX_AXES + 2, IconHomeU, evSendCommand, "G28 U0"); + homeButtons[3]->Show(false); +#endif +#if MAX_AXES > 4 + homeButtons[4] = AddIconButton(row7p7, 5, MAX_AXES + 2, IconHomeV, evSendCommand, "G28 V0"); + homeButtons[4]->Show(false); +#endif +#if MAX_AXES > 5 + homeButtons[5] = AddIconButton(row7p7, 6, MAX_AXES + 2, IconHomeW, evSendCommand, "G28 W0"); + homeButtons[5]->Show(false); +#endif + DisplayField::SetDefaultColours(colours.buttonTextColour, colours.buttonImageBackColour); + bedCompButton = AddIconButton(row7p7, MAX_AXES + 1, MAX_AXES + 2, IconBedComp, evSendCommand, "G32"); + + filesButton = AddIconButton(row8p7, 0, 4, IconFiles, evListFiles, nullptr); + DisplayField::SetDefaultColours(colours.buttonTextColour, colours.buttonTextBackColour); + moveButton = AddTextButton(row8p7, 1, 4, strings->move, evMovePopup, nullptr); + extrudeButton = AddTextButton(row8p7, 2, 4, strings->extrusion, evExtrudePopup, nullptr); + macroButton = AddTextButton(row8p7, 3, 4, strings->macro, evListMacros, nullptr); + + controlRoot = mgr.GetRoot(); +} + +// Create the fields for the Printing tab +void CreatePrintingTabFields(const ColourScheme& colours) +{ + mgr.SetRoot(commonRoot); + + // Labels + DisplayField::SetDefaultColours(colours.labelTextColour, colours.defaultBackColour); + mgr.AddField(new StaticTextField(row6 + labelRowAdjust, margin, bedColumn - fieldSpacing, TextAlignment::Right, strings->extruderPercent)); + + // Extrusion factor buttons + DisplayField::SetDefaultColours(colours.buttonTextColour, colours.buttonTextBackColour); + for (unsigned int i = 1; i < maxHeaters; ++i) + { + PixelNumber column = ((tempButtonWidth + fieldSpacing) * i) + bedColumn; + + IntegerButton *ib = new IntegerButton(row6, column, tempButtonWidth); + ib->SetValue(100); + ib->SetEvent(evExtrusionFactor, i); + extrusionFactors[i - 1] = ib; + mgr.AddField(ib); + } + + // Speed button + mgr.AddField(spd = new IntegerButton(row7, speedColumn, fanColumn - speedColumn - fieldSpacing, strings->speed, "%")); + spd->SetValue(100); + spd->SetEvent(evAdjustSpeed, "M220 S"); + + // Fan button + mgr.AddField(fanSpeed = new IntegerButton(row7, fanColumn, pauseColumn - fanColumn - fieldSpacing, strings->fan, "%")); + fanSpeed->SetEvent(evAdjustFan, 0); + fanSpeed->SetValue(0); + + DisplayField::SetDefaultColours(colours.buttonTextColour, colours.pauseButtonBackColour); + pauseButton = new TextButton(row7, pauseColumn, babystepColumn - pauseColumn - fieldSpacing, strings->pause, evPausePrint, "M25"); + mgr.AddField(pauseButton); + + DisplayField::SetDefaultColours(colours.buttonTextColour, colours.buttonTextBackColour); + babystepButton = new TextButton(row7, babystepColumn, DISPLAY_X - babystepColumn - fieldSpacing, strings->babystep, evBabyStepPopup); + mgr.AddField(babystepButton); + + DisplayField::SetDefaultColours(colours.buttonTextColour, colours.resumeButtonBackColour); + resumeButton = new TextButton(row7, resumeColumn, cancelColumn - resumeColumn - fieldSpacing, strings->resume, evResumePrint, "M24"); + mgr.AddField(resumeButton); + + DisplayField::SetDefaultColours(colours.buttonTextColour, colours.resetButtonBackColour); + resetButton = new TextButton(row7, cancelColumn, DISPLAY_X - cancelColumn - margin, strings->cancel, evReset, "M0"); + mgr.AddField(resetButton); + + // DisplayField::SetDefaultColours(labelTextColour, defaultBackColour); + // mgr.AddField(printingField = new TextField(row8, margin, DISPLAY_X, TextAlignment::Left, "printing ", printingFile.c_str())); + + DisplayField::SetDefaultColours(colours.progressBarColour,colours. progressBarBackColour); + mgr.AddField(printProgressBar = new ProgressBar(row8 + (rowHeight - progressBarHeight)/2, margin, progressBarHeight, DISPLAY_X - 2 * margin)); + mgr.Show(printProgressBar, false); + + DisplayField::SetDefaultColours(colours.labelTextColour, colours.defaultBackColour); + mgr.AddField(timeLeftField = new TextField(row9, margin, DISPLAY_X - 2 * margin, TextAlignment::Left, strings->timeRemaining)); + mgr.Show(timeLeftField, false); + + printRoot = mgr.GetRoot(); +} + +// Create the fields for the Message tab +void CreateMessageTabFields(const ColourScheme& colours) +{ + mgr.SetRoot(baseRoot); + DisplayField::SetDefaultColours(colours.buttonTextColour, colours.buttonImageBackColour); + mgr.AddField(new IconButton(margin, DISPLAY_X - margin - keyboardButtonWidth, keyboardButtonWidth, IconKeyboard, evKeyboard)); + DisplayField::SetDefaultColours(colours.labelTextColour, colours.defaultBackColour); + mgr.AddField(new StaticTextField(margin + labelRowAdjust, margin, DISPLAY_X - 2 * margin - keyboardButtonWidth, TextAlignment::Centre, strings->messages)); + PixelNumber row = firstMessageRow; + for (unsigned int r = 0; r < numMessageRows; ++r) + { + StaticTextField *t = new StaticTextField(row, margin, messageTimeWidth, TextAlignment::Left, nullptr); + mgr.AddField(t); + messageTimeFields[r] = t; + t = new StaticTextField(row, messageTextX, messageTextWidth, TextAlignment::Left, nullptr); + mgr.AddField(t); + messageTextFields[r] = t; + row += rowTextHeight; + } + messageRoot = mgr.GetRoot(); +} + +// Create the fields for the Setup tab +void CreateSetupTabFields(uint32_t language, const ColourScheme& colours) +{ + mgr.SetRoot(baseRoot); + DisplayField::SetDefaultColours(colours.labelTextColour, colours.defaultBackColour); + // The firmware version field doubles up as an area for displaying debug messages, so make it the full width of the display + mgr.AddField(fwVersionField = new TextField(row1, margin, DISPLAY_X, TextAlignment::Left, strings->firmwareVersion, VERSION_TEXT)); + mgr.AddField(freeMem = new IntegerField(row2, margin, DISPLAY_X/2 - margin, TextAlignment::Left, "Free RAM: ")); + mgr.AddField(touchX = new IntegerField(row2, DISPLAY_X/2, DISPLAY_X/4, TextAlignment::Left, "Touch: ", ",")); + mgr.AddField(touchY = new IntegerField(row2, (DISPLAY_X * 3)/4, DISPLAY_X/4, TextAlignment::Left)); + + DisplayField::SetDefaultColours(colours.errorTextColour, colours.errorBackColour); + mgr.AddField(settingsNotSavedField = new StaticTextField(row3, margin, DISPLAY_X - 2 * margin, TextAlignment::Left, strings->settingsNotSavedText)); + settingsNotSavedField->Show(false); + + DisplayField::SetDefaultColours(colours.buttonTextColour, colours.buttonTextBackColour); + baudRateButton = AddIntegerButton(row4, 0, 3, nullptr, " baud", evSetBaudRate); + baudRateButton->SetValue(GetBaudRate()); + volumeButton = AddIntegerButton(row4, 1, 3, strings->volume, nullptr, evSetVolume); + volumeButton->SetValue(GetVolume()); + languageButton = AddTextButton(row4, 2, 3, LanguageTables[language].languageName, evSetLanguage, nullptr); + AddTextButton(row5, 0, 3, strings->calibrateTouch, evCalTouch, nullptr); + AddTextButton(row5, 1, 3, strings->mirrorDisplay, evInvertX, nullptr); + AddTextButton(row5, 2, 3, strings->invertDisplay, evInvertY, nullptr); + coloursButton = AddTextButton(row6, 0, 3, strings->colourSchemeNames[colours.index], evSetColours, nullptr); + coloursButton->SetText(strings->colourSchemeNames[colours.index]); + AddTextButton(row6, 1, 3, strings->brightnessDown, evDimmer, nullptr); + AddTextButton(row6, 2, 3, strings->brightnessUp, evBrighter, nullptr); + AddTextButton(row7, 0, 3, strings->saveSettings, evSaveSettings, nullptr); + AddTextButton(row7, 1, 3, strings->clearSettings, evFactoryReset, nullptr); + AddTextButton(row7, 2, 3, strings->saveAndRestart, evRestart, nullptr); + setupRoot = mgr.GetRoot(); +} + +// Create the fields that are displayed on all pages +void CreateCommonFields(const ColourScheme& colours) +{ + DisplayField::SetDefaultColours(colours.buttonTextColour, colours.buttonTextBackColour, colours.buttonBorderColour, colours.buttonGradColour, + colours.buttonPressedBackColour, colours.buttonPressedGradColour, colours.pal); + tabControl = AddTextButton(rowTabs, 0, 4, strings->control, evTabControl, nullptr); + tabPrint = AddTextButton(rowTabs, 1, 4, strings->print, evTabPrint, nullptr); + tabMsg = AddTextButton(rowTabs, 2, 4, strings->console, evTabMsg, nullptr); + tabSetup = AddTextButton(rowTabs, 3, 4, strings->setup, evTabSetup, nullptr); +} + +void CreateMainPages(uint32_t language, const ColourScheme& colours) +{ + if (language >= ARRAY_SIZE(LanguageTables)) + { + language = 0; + } + strings = &LanguageTables[language]; + CreateCommonFields(colours); + baseRoot = mgr.GetRoot(); // save the root of fields that we usually display + + // Create the fields that are common to the Control and Print pages + DisplayField::SetDefaultColours(colours.titleBarTextColour, colours.titleBarBackColour); + mgr.AddField(nameField = new StaticTextField(row1, 0, DISPLAY_X - statusFieldWidth, TextAlignment::Centre, machineName.c_str())); + mgr.AddField(statusField = new StaticTextField(row1, DISPLAY_X - statusFieldWidth, statusFieldWidth, TextAlignment::Right, nullptr)); + CreateTemperatureGrid(colours); + commonRoot = mgr.GetRoot(); // save the root of fields that we display on more than one page + + // Create the pages + CreateControlTabFields(colours); + CreatePrintingTabFields(colours); + CreateMessageTabFields(colours); + CreateSetupTabFields(language, colours); +} + +namespace UI +{ + static void Adjusting(ButtonPress bp) + { + fieldBeingAdjusted = bp; + if (bp == currentButton) + { + currentButton.Clear(); // to stop it being released + } + } + + static void StopAdjusting() + { + if (fieldBeingAdjusted.IsValid()) + { + mgr.Press(fieldBeingAdjusted, false); + fieldBeingAdjusted.Clear(); + } + } + + static void CurrentButtonReleased() + { + if (currentButton.IsValid()) + { + mgr.Press(currentButton, false); + currentButton.Clear(); + } + } + + // Return the number of supported languages + extern unsigned int GetNumLanguages() + { + return numLanguages; + } + + // Create all the fields we ever display + void CreateFields(uint32_t language, const ColourScheme& colours) + { + // Set up default colours and margins + mgr.Init(colours.defaultBackColour); + DisplayField::SetDefaultFont(DEFAULT_FONT); + ButtonWithText::SetFont(DEFAULT_FONT); + SingleButton::SetTextMargin(textButtonMargin); + SingleButton::SetIconMargin(iconButtonMargin); + + // Create the pages + CreateMainPages(language, colours); + + // Create the popup fields + CreateIntegerAdjustPopup(colours); + CreateMovePopup(colours); + CreateExtrudePopup(colours); + CreateFileListPopup(colours); + CreateFileActionPopup(colours); + CreateVolumePopup(colours); + CreateBaudRatePopup(colours); + CreateColoursPopup(colours); + CreateAreYouSurePopup(colours); + CreateKeyboardPopup(language, colours); + CreateLanguagePopup(colours); + CreateMessagePopup(colours); + CreateBabystepPopup(colours); + + DisplayField::SetDefaultColours(colours.labelTextColour, colours.defaultBackColour); + touchCalibInstruction = new StaticTextField(DISPLAY_Y/2 - 10, 0, DISPLAY_X, TextAlignment::Centre, strings->touchTheSpot); + + mgr.SetRoot(nullptr); + } + + // Show or hide the field that warns about unsaved settings + void CheckSettingsAreSaved() + { + if (IsSaveAndRestartNeeded()) + { + settingsNotSavedField->SetValue(strings->restartNeededText); + mgr.Show(settingsNotSavedField, true); + } + else if (IsSaveNeeded()) + { + settingsNotSavedField->SetValue(strings->settingsNotSavedText); + mgr.Show(settingsNotSavedField, true); + } + else + { + mgr.Show(settingsNotSavedField, false); + } + } + + void ShowFilesButton() + { + mgr.Show(resumeButton, false); + mgr.Show(resetButton, false); + mgr.Show(pauseButton, false); + mgr.Show(babystepButton, false); + mgr.Show(filesButton, true); + } + + void ShowPauseButton() + { + mgr.Show(resumeButton, false); + mgr.Show(resetButton, false); + mgr.Show(filesButton, false); + mgr.Show(pauseButton, true); + mgr.Show(babystepButton, true); + } + + void ShowResumeAndCancelButtons() + { + mgr.Show(pauseButton, false); + mgr.Show(babystepButton, false); + mgr.Show(filesButton, false); + mgr.Show(resumeButton, true); + mgr.Show(resetButton, true); + } + + // Show or hide an axis on the move button grid and on the axis display + void ShowAxis(size_t axis, bool b) + { + // The table gives us a pointer to the label field, which is followed by 8 buttons. So we need to show or hide 9 fields. + DisplayField *f = moveAxisRows[axis]; + for (int i = 0; i < 9 && f != nullptr; ++i) + { + f->Show(b); + f = f->next; + } + axisPos[axis]->Show(b); + } + + void UpdateAxisPosition(size_t axis, float fval) + { + if (axis < MAX_AXES && axisPos[axis] != nullptr) + { + axisPos[axis]->SetValue(fval); + } + } + + void UpdateCurrentTemperature(size_t heater, float fval) + { + if (currentTemps[heater] != nullptr) + { + currentTemps[heater]->SetValue(fval); + } + } + + void ShowHeater(size_t heater, bool show) + { + mgr.Show(currentTemps[heater], show); + mgr.Show(activeTemps[heater], show); + mgr.Show(standbyTemps[heater], show); + mgr.Show(extrusionFactors[heater - 1], show); + } + + void UpdateHeaterStatus(size_t heater, int ival) + { + heaterStatus[heater] = ival; + if (currentTemps[heater] != nullptr) + { + Colour c = (ival == 1) ? colours->standbyBackColour + : (ival == 2) ? colours->activeBackColour + : (ival == 3) ? colours->errorBackColour + : (ival == 4) ? colours->tuningBackColour + : colours->defaultBackColour; + currentTemps[heater]->SetColours((ival == 3) ? colours->errorTextColour : colours->infoTextColour, c); + } + } + + static int timesLeft[3]; + static String<50> timesLeftText; + + void ChangeStatus(PrinterStatus oldStatus, PrinterStatus newStatus) + { + switch (newStatus) + { + case PrinterStatus::printing: + if (oldStatus != PrinterStatus::paused && oldStatus != PrinterStatus::resuming) + { + // Starting a new print, so clear the times + timesLeft[0] = timesLeft[1] = timesLeft[2] = 0; + } + // no break + case PrinterStatus::paused: + case PrinterStatus::pausing: + case PrinterStatus::resuming: + if (oldStatus == PrinterStatus::connecting || oldStatus == PrinterStatus::idle) + { + ChangePage(tabPrint); + } + else if (currentTab == tabPrint) + { + nameField->SetValue(printingFile.c_str()); + } + break; + + case PrinterStatus::idle: + printingFile.clear(); + nameField->SetValue(machineName.c_str()); // if we are on the print tab then it may still be set to the file that was being printed + // no break + case PrinterStatus::configuring: + if (oldStatus == PrinterStatus::flashing) + { + mgr.ClearAllPopups(); // clear the firmware update message + } + break; + + case PrinterStatus::connecting: + printingFile.clear(); + // We no longer clear the machine name here + mgr.ClearAllPopups(); + break; + + default: + nameField->SetValue(machineName.c_str()); + break; + } + } + + // Append an amount of time to timesLeftText + static void AppendTimeLeft(int t) + { + if (t <= 0) + { + timesLeftText.catFrom(strings->notAvailable); + } + else if (t < 60) + { + timesLeftText.catf("%ds", t); + } + else if (t < 60 * 60) + { + timesLeftText.catf("%dm %02ds", t/60, t%60); + } + else + { + t /= 60; + timesLeftText.catf("%dh %02dm", t/60, t%60); + } + } + + void UpdateTimesLeft(size_t index, unsigned int seconds) + { + if (index < (int)ARRAY_SIZE(timesLeft)) + { + timesLeft[index] = seconds; + timesLeftText.copy(strings->file); + AppendTimeLeft(timesLeft[0]); + timesLeftText.catFrom(strings->filament); + AppendTimeLeft(timesLeft[1]); + if (DISPLAY_X >= 800) + { + timesLeftText.catFrom(strings->layer); + AppendTimeLeft(timesLeft[2]); + } + timeLeftField->SetValue(timesLeftText.c_str()); + mgr.Show(timeLeftField, true); + } + } + + // Change to the page indicated. Return true if the page has a permanently-visible button. + bool ChangePage(ButtonBase *newTab) + { + if (newTab != currentTab) + { + if (currentTab != nullptr) + { + currentTab->Press(false, 0); // remove highlighting from the old tab + } + newTab->Press(true, 0); // highlight the new tab + currentTab = newTab; + mgr.ClearAllPopups(); + switch(newTab->GetEvent()) + { + case evTabControl: + mgr.SetRoot(controlRoot); + nameField->SetValue(machineName.c_str()); + break; + case evTabPrint: + mgr.SetRoot(printRoot); + nameField->SetValue(PrintInProgress() ? printingFile.c_str() : machineName.c_str()); + break; + case evTabMsg: + mgr.SetRoot(messageRoot); + if (keyboardIsDisplayed) + { + mgr.SetPopup(keyboardPopup, AutoPlace, keyboardPopupY, false); + } + break; + case evTabSetup: + mgr.SetRoot(setupRoot); + break; + default: + mgr.SetRoot(commonRoot); + break; + } + mgr.Refresh(true); + } + return true; + } + + // Pop up the keyboard + void ShowKeyboard() + { + mgr.SetPopup(keyboardPopup, AutoPlace, keyboardPopupY); + keyboardIsDisplayed = true; + } + + // This is called when the Cancel button on a popup is pressed + void PopupCancelled() + { + if (mgr.GetPopup() == keyboardPopup) + { + keyboardIsDisplayed = false; + } + } + + // Return true if polling should be performed + bool DoPolling() + { + return currentTab != tabSetup; // don't poll while we are on the Setup page + } + + // This is called in the main spin loop + void Spin() + { + if (currentTab == tabMsg) + { + MessageLog::UpdateMessages(false); + } + } + + // This is called when we have just started a file print + void PrintStarted() + { + ChangePage(tabPrint); + } + + // This is called when we have just received the name of the file being printed + void PrintingFilenameChanged(const char data[]) + { + if (!printingFile.similar(data)) + { + printingFile.copy(data); + if (currentTab == tabPrint && PrintInProgress()) + { + nameField->SetChanged(); + } + } + } + + // This is called just before the main polling loop starts. Display the default page. + void ShowDefaultPage() + { + ChangePage(tabControl); + } + + // Update the fields that are to do with the printing status + void UpdatePrintingFields() + { + if (GetStatus() == PrinterStatus::printing) + { + ShowPauseButton(); + } + else if (GetStatus() == PrinterStatus::paused) + { + ShowResumeAndCancelButtons(); + } + else + { + ShowFilesButton(); + } + + mgr.Show(printProgressBar, PrintInProgress()); + // mgr.Show(printingField, PrintInProgress()); + + // Don't enable the time left field when we start printing, instead this will get enabled when we receive a suitable message + if (!PrintInProgress()) + { + mgr.Show(timeLeftField, false); + } + + statusField->SetValue(strings->statusValues[(unsigned int)GetStatus()]); + } + + // Set the percentage of print completed + void SetPrintProgressPercent(unsigned int percent) + { + printProgressBar->SetPercent((uint8_t)percent); + } + + // Update the geometry or the number of axes + void UpdateGeometry(unsigned int numAxes, bool isDelta) + { + for (size_t i = 0; i < MAX_AXES; ++i) + { + mgr.Show(homeButtons[i], !isDelta && i < numAxes); + ShowAxis(i, i < numAxes); + } + } + + // Update the homed status of the specified axis. If the axis is -1 then it represents the "all homed" status. + void UpdateHomedStatus(int axis, bool isHomed) + { + SingleButton *homeButton = nullptr; + if (axis < 0) + { + homeButton = homeAllButton; + } + else if (axis < MAX_AXES) + { + homeButton = homeButtons[axis]; + } + if (homeButton != nullptr) + { + homeButton->SetColours(colours->buttonTextColour, (isHomed) ? colours->homedButtonBackColour : colours->notHomedButtonBackColour); + } + } + + // UIpdate the Z probe text + void UpdateZProbe(const char data[]) + { + zprobeBuf.copy(data); + zProbe->SetChanged(); + } + + // Update the machine name + void UpdateMachineName(const char data[]) + { + machineName.copy(data); + nameField->SetChanged(); + } + + // Update the fan RPM + void UpdateFanPercent(int rpm) + { + UpdateField(fanSpeed, rpm); + } + + // Update an active temperature + void UpdateActiveTemperature(size_t index, int ival) + { + UpdateField(activeTemps[index], ival); + } + + // Update a standby temperature + void UpdateStandbyTemperature(size_t index, int ival) + { + UpdateField(standbyTemps[index], ival); + } + + // Update an extrusion factor + void UpdateExtrusionFactor(size_t index, int ival) + { + UpdateField(extrusionFactors[index], ival); + } + + // Update the print speed factor + void UpdateSpeedPercent(int ival) + { + UpdateField(spd, ival); + } + + // Process an alert message. If the data is empty then we should clear any existing alert. + void ProcessAlert(const char data[]) + { + if (data[0] == 0) + { + mgr.ClearPopup(true, alertPopup); + } + else + { + alertText.copy(data); + mgr.SetPopup(alertPopup, AutoPlace, AutoPlace); + } + } + + // This is called when the user selects a new file from a list of SD card files + void FileSelected(const char * array null fileName) + { + fpNameField->SetValue(fileName); + // Clear out the old field values, they relate to the previous file we looked at until we process the response + fpSizeField->SetValue(0); // would be better to make it blank + fpHeightField->SetValue(0.0); // would be better to make it blank + fpLayerHeightField->SetValue(0.0); // would be better to make it blank + fpFilamentField->SetValue(0); // would be better to make it blank + generatedByText.clear(); + fpGeneratedByField->SetChanged(); + } + + // This is called when the "generated by" file information has been received + void UpdateFileGeneratedByText(const char data[]) + { + generatedByText.copy(data); + fpGeneratedByField->SetChanged(); + } + + // This is called when the object height information for the file has been received + void UpdateFileObjectHeight(float f) + { + fpHeightField->SetValue(f); + } + + // This is called when the layer height information for the file has been received + void UpdateFileLayerHeight(float f) + { + fpLayerHeightField->SetValue(f); + } + + // This is called when the size of the file has been received + void UpdateFileSize(int size) + { + fpSizeField->SetValue(size); + } + + // This is called when the filament needed by the file has been received + void UpdateFileFilament(int len) + { + fpFilamentField->SetValue(len); + } + + // Return true if we are displaying file information + bool IsDisplayingFileInfo() + { + return currentFile != nullptr; + } + + // This is called when the host firmware changes + void FirmwareFeaturesChanged(FirmwareFeatures newFeatures) + { + // Some firmwares don't support tool standby temperatures + for (size_t i = 0; i < maxHeaters; ++i) + { + mgr.Show(standbyTemps[i], (newFeatures & noStandbyTemps) == 0); + } + } + + // Process a touch event + void ProcessTouch(ButtonPress bp) + { + if (bp.IsValid()) + { + ButtonBase *f = bp.GetButton(); + currentButton = bp; + mgr.Press(bp, true); + Event ev = (Event)(f->GetEvent()); + switch(ev) + { + case evEmergencyStop: + { + SerialIo::SendString("M112\n"); + Delay(1000); + SerialIo::SendString("M999\n"); + Reconnect(); + } + break; + + case evTabControl: + case evTabPrint: + case evTabMsg: + case evTabSetup: + if (ChangePage(f)) + { + currentButton.Clear(); // keep the button highlighted after it is released + } + break; + + case evAdjustActiveTemp: + case evAdjustStandbyTemp: + if (static_cast(f)->GetValue() < 0) + { + static_cast(f)->SetValue(0); + } + Adjusting(bp); + mgr.SetPopup(setTempPopup, AutoPlace, popupY); + break; + + case evAdjustSpeed: + case evExtrusionFactor: + case evAdjustFan: + oldIntValue = static_cast(bp.GetButton())->GetValue(); + Adjusting(bp); + mgr.SetPopup(setTempPopup, AutoPlace, popupY); + break; + + case evSetInt: + if (fieldBeingAdjusted.IsValid()) + { + int val = static_cast(fieldBeingAdjusted.GetButton())->GetValue(); + switch(fieldBeingAdjusted.GetEvent()) + { + case evAdjustActiveTemp: + { + int heater = fieldBeingAdjusted.GetIParam(); + if (heater == 0) + { + SerialIo::SendString("M140 S"); + SerialIo::SendInt(val); + SerialIo::SendChar('\n'); + } + else + { + SerialIo::SendString(((GetFirmwareFeatures() & noG10Temps) == 0) ? "G10 P" : "M104 T"); + SerialIo::SendInt(heater - 1); + SerialIo::SendString(" S"); + SerialIo::SendInt(val); + SerialIo::SendChar('\n'); + } + } + break; + + case evAdjustStandbyTemp: + { + int heater = fieldBeingAdjusted.GetIParam(); + if (heater > 0) + { + SerialIo::SendString("G10 P"); + SerialIo::SendInt(heater - 1); + SerialIo::SendString(" R"); + SerialIo::SendInt(val); + SerialIo::SendChar('\n'); + } + } + break; + + case evExtrusionFactor: + { + int heater = fieldBeingAdjusted.GetIParam(); + SerialIo::SendString("M221 P"); + SerialIo::SendInt(heater); + SerialIo::SendString(" S"); + SerialIo::SendInt(val); + SerialIo::SendChar('\n'); + } + break; + + case evAdjustFan: + SerialIo::SendString("M106 S"); + SerialIo::SendInt((256 * val)/100); + SerialIo::SendChar('\n'); + break; + + default: + { + const char* null cmd = fieldBeingAdjusted.GetSParam(); + if (cmd != nullptr) + { + SerialIo::SendString(cmd); + SerialIo::SendInt(val); + SerialIo::SendChar('\n'); + } + } + break; + } + mgr.ClearPopup(); + StopAdjusting(); + } + break; + + case evAdjustInt: + if (fieldBeingAdjusted.IsValid()) + { + IntegerButton *ib = static_cast(fieldBeingAdjusted.GetButton()); + int newValue = ib->GetValue() + bp.GetIParam(); + switch(fieldBeingAdjusted.GetEvent()) + { + case evAdjustActiveTemp: + case evAdjustStandbyTemp: + newValue = constrain(newValue, 0, 1600); // some users want to print at high temperatures + break; + + case evAdjustFan: + newValue = constrain(newValue, 0, 100); + break; + + default: + break; + } + ib->SetValue(newValue); + ShortenTouchDelay(); + } + break; + + case evMovePopup: + mgr.SetPopup(movePopup, AutoPlace, AutoPlace); + break; + + case evMoveX: + case evMoveY: + case evMoveZ: + case evMoveU: + case evMoveV: + case evMoveW: + { + const uint8_t axis = ev - evMoveX; + const char c = (axis < 3) ? 'X' + axis : ('U' - 3) + axis; + SerialIo::SendString("G91\nG1 "); + SerialIo::SendChar(c); + SerialIo::SendString(bp.GetSParam()); + SerialIo::SendString(" F6000\nG90\n"); + } + break; + + case evExtrudePopup: + mgr.SetPopup(extrudePopup, AutoPlace, AutoPlace); + break; + + case evExtrudeAmount: + mgr.Press(currentExtrudeAmountPress, false); + mgr.Press(bp, true); + currentExtrudeAmountPress = bp; + currentButton.Clear(); // stop it being released by the timer + break; + + case evExtrudeRate: + mgr.Press(currentExtrudeRatePress, false); + mgr.Press(bp, true); + currentExtrudeRatePress = bp; + currentButton.Clear(); // stop it being released by the timer + break; + + case evExtrude: + case evRetract: + if (currentExtrudeAmountPress.IsValid() && currentExtrudeRatePress.IsValid()) + { + SerialIo::SendString("G92 E0\nG1 E"); + if (ev == evRetract) + { + SerialIo::SendChar('-'); + } + SerialIo::SendString(currentExtrudeAmountPress.GetSParam()); + SerialIo::SendString(" F"); + SerialIo::SendString(currentExtrudeRatePress.GetSParam()); + SerialIo::SendChar('\n'); + } + break; + + case evBabyStepPopup: + mgr.SetPopup(babystepPopup, AutoPlace, AutoPlace); + break; + + case evBabyStepAmount: + SerialIo::SendString("M290 "); + SerialIo::SendString(bp.GetSParam()); + SerialIo::SendChar('\n'); + break; + + case evListFiles: + FileManager::DisplayFilesList(); + break; + + case evListMacros: + FileManager::DisplayMacrosList(); + break; + + case evCalTouch: + CalibrateTouch(); + CheckSettingsAreSaved(); + break; + + case evFactoryReset: + PopupAreYouSure(ev, strings->confirmFactoryReset); + break; + + case evRestart: + PopupAreYouSure(ev, strings->confirmRestart); + break; + + case evSaveSettings: + SaveSettings(); + if (restartNeeded) + { + PopupRestart(); + } + break; + + case evSelectHead: + { + int head = bp.GetIParam(); + if (head == 0) + { + if (heaterStatus[0] == 2) // if bed is active + { + SerialIo::SendString("M144\n"); + } + else + { + SerialIo::SendString("M140 S"); + SerialIo::SendInt(activeTemps[0]->GetValue()); + SerialIo::SendChar('\n'); + } + } + else if (head < (int)maxHeaters) + { + if (heaterStatus[head] == 2) // if head is active + { + SerialIo::SendString("T-1\n"); + } + else + { + SerialIo::SendChar('T'); + SerialIo::SendInt(head - 1); + SerialIo::SendChar('\n'); + } + } + } + break; + + case evFile: + { + const char * array fileName = bp.GetSParam(); + if (fileName != nullptr) + { + if (fileName[0] == '*') + { + // It's a directory + FileManager::RequestFilesSubdir(fileName + 1); + //??? need to pop up a "wait" box here + } + else + { + // It's a regular file + currentFile = fileName; + SerialIo::SendString(((GetFirmwareFeatures() & noM20M36) == 0) ? "M36 " : "M408 S36 P"); // ask for the file info + SerialIo::SendFilename(CondStripDrive(FileManager::GetFilesDir()), currentFile); + SerialIo::SendChar('\n'); + FileSelected(currentFile); + mgr.SetPopup(filePopup, AutoPlace, AutoPlace); + } + } + else + { + ErrorBeep(); + } + } + break; + + case evFilesUp: + FileManager::RequestFilesParentDir(); + break; + + case evMacrosUp: + FileManager::RequestMacrosParentDir(); + break; + + case evMacro: + { + const char *fileName = bp.GetSParam(); + if (fileName != nullptr) + { + if (fileName[0] == '*') // if it's a directory + { + FileManager::RequestMacrosSubdir(fileName + 1); + //??? need to pop up a "wait" box here + } + else + { + SerialIo::SendString("M98 P"); + SerialIo::SendFilename(CondStripDrive(FileManager::GetMacrosDir()), fileName); + SerialIo::SendChar('\n'); + } + } + else + { + ErrorBeep(); + } + } + break; + + case evPrint: + mgr.ClearPopup(); // clear the file info popup + mgr.ClearPopup(); // clear the file list popup + if (currentFile != nullptr) + { + SerialIo::SendString("M32 "); + SerialIo::SendFilename(CondStripDrive(StripPrefix(FileManager::GetFilesDir())), currentFile); + SerialIo::SendChar('\n'); + PrintingFilenameChanged(currentFile); + currentFile = nullptr; // allow the file list to be updated + CurrentButtonReleased(); + PrintStarted(); + } + break; + + case evCancel: + eventToConfirm = evNull; + currentFile = nullptr; + CurrentButtonReleased(); + PopupCancelled(); + mgr.ClearPopup(); + break; + + case evDeleteFile: + CurrentButtonReleased();; + PopupAreYouSure(ev, strings->confirmFileDelete); + break; + + case evSendCommand: + case evPausePrint: + case evResumePrint: + case evReset: + SerialIo::SendString(bp.GetSParam()); + SerialIo::SendChar('\n'); + break; + + case evScrollFiles: + FileManager::Scroll(bp.GetIParam() * GetNumScrolledFiles()); + ShortenTouchDelay(); + break; + + case evChangeCard: + (void)FileManager::NextCard(); + break; + + case evKeyboard: + ShowKeyboard(); + break; + + case evInvertX: + MirrorDisplay(); + CalibrateTouch(); + CheckSettingsAreSaved(); + break; + + case evInvertY: + InvertDisplay(); + CalibrateTouch(); + CheckSettingsAreSaved(); + break; + + case evSetBaudRate: + Adjusting(bp); + mgr.SetPopup(baudPopup, AutoPlace, popupY); + break; + + case evAdjustBaudRate: + { + const int rate = bp.GetIParam(); + SetBaudRate(rate); + baudRateButton->SetValue(rate); + } + CheckSettingsAreSaved(); + CurrentButtonReleased(); + mgr.ClearPopup(); + StopAdjusting(); + break; + + case evSetVolume: + Adjusting(bp); + mgr.SetPopup(volumePopup, AutoPlace, popupY); + break; + + case evSetColours: + if (coloursPopup != nullptr) + { + Adjusting(bp); + mgr.SetPopup(coloursPopup, AutoPlace, popupY); + } + break; + + case evBrighter: + case evDimmer: + ChangeBrightness(ev == evBrighter); + CheckSettingsAreSaved(); + ShortenTouchDelay(); + break; + + case evAdjustVolume: + { + const int newVolume = bp.GetIParam(); + SetVolume(newVolume); + volumeButton->SetValue(newVolume); + } + TouchBeep(); // give audible feedback of the touch at the new volume level + CheckSettingsAreSaved(); + break; + + case evAdjustColours: + { + const int newColours = bp.GetIParam(); + SetColourScheme(newColours); + coloursButton->SetText(strings->colourSchemeNames[newColours]); + } + CheckSettingsAreSaved(); + break; + + case evSetLanguage: + Adjusting(bp); + mgr.SetPopup(languagePopup, AutoPlace, popupY); + break; + + case evAdjustLanguage: + { + const int newLanguage = bp.GetIParam(); + SetLanguage(newLanguage); + languageButton->SetText(LanguageTables[newLanguage].languageName); + } + CheckSettingsAreSaved(); // not sure we need this because we are going to reset anyway + break; + + case evYes: + CurrentButtonReleased(); + mgr.ClearPopup(); // clear the yes/no popup + switch (eventToConfirm) + { + case evFactoryReset: + FactoryReset(); + break; + + case evDeleteFile: + if (currentFile != nullptr) + { + mgr.ClearPopup(); // clear the file info popup + SerialIo::SendString("M30 "); + SerialIo::SendFilename(CondStripDrive(StripPrefix(FileManager::GetFilesDir())), currentFile); + SerialIo::SendChar('\n'); + FileManager::RefreshFilesList(); + currentFile = nullptr; + } + break; + + case evRestart: + if (IsSaveNeeded()) + { + SaveSettings(); + } + Restart(); + break; + + default: + break; + } + eventToConfirm = evNull; + currentFile = nullptr; + break; + + case evKey: + if (!userCommandBuffers[currentUserCommandBuffer].full()) + { + userCommandBuffers[currentUserCommandBuffer].add((char)bp.GetIParam()); + userCommandField->SetChanged(); + } + break; + + case evBackspace: + if (!userCommandBuffers[currentUserCommandBuffer].isEmpty()) + { + userCommandBuffers[currentUserCommandBuffer].erase(userCommandBuffers[currentUserCommandBuffer].size() - 1); + userCommandField->SetChanged(); + ShortenTouchDelay(); + } + break; + + case evUp: + currentHistoryBuffer = (currentHistoryBuffer + numUserCommandBuffers - 1) % numUserCommandBuffers; + if (currentHistoryBuffer == currentUserCommandBuffer) + { + userCommandBuffers[currentUserCommandBuffer].clear(); + } + else + { + userCommandBuffers[currentUserCommandBuffer].copy(userCommandBuffers[currentHistoryBuffer]); + } + userCommandField->SetChanged(); + break; + + case evDown: + currentHistoryBuffer = (currentHistoryBuffer + 1) % numUserCommandBuffers; + if (currentHistoryBuffer == currentUserCommandBuffer) + { + userCommandBuffers[currentUserCommandBuffer].clear(); + } + else + { + userCommandBuffers[currentUserCommandBuffer].copy(userCommandBuffers[currentHistoryBuffer]); + } + userCommandField->SetChanged(); + break; + + case evSendKeyboardCommand: + if (userCommandBuffers[currentUserCommandBuffer].size() != 0) + { + SerialIo::SendString(userCommandBuffers[currentUserCommandBuffer].c_str()); + SerialIo::SendChar('\n'); + + // Add the command to the history if it was different frmo the previous command + size_t prevBuffer = (currentUserCommandBuffer + numUserCommandBuffers - 1) % numUserCommandBuffers; + if (strcmp(userCommandBuffers[currentUserCommandBuffer].c_str(), userCommandBuffers[prevBuffer].c_str()) != 0) + { + currentUserCommandBuffer = (currentUserCommandBuffer + 1) % numUserCommandBuffers; + } + currentHistoryBuffer = currentUserCommandBuffer; + userCommandBuffers[currentUserCommandBuffer].clear(); + userCommandField->SetLabel(userCommandBuffers[currentUserCommandBuffer].c_str()); + } + break; + + default: + break; + } + } + } + + // Process a touch event outside the popup on the field being adjusted + void ProcessTouchOutsidePopup(ButtonPress bp) + { + if (bp == fieldBeingAdjusted) + { + DelayTouchLong(); // by default, ignore further touches for a long time + TouchBeep(); + switch(fieldBeingAdjusted.GetEvent()) + { + case evAdjustSpeed: + case evExtrusionFactor: + case evAdjustFan: + static_cast(fieldBeingAdjusted.GetButton())->SetValue(oldIntValue); + mgr.ClearPopup(); + StopAdjusting(); + break; + + case evAdjustActiveTemp: + case evAdjustStandbyTemp: + case evSetBaudRate: + case evSetVolume: + case evSetColours: + mgr.ClearPopup(); + StopAdjusting(); + break; + + case evSetLanguage: + mgr.ClearPopup(); + StopAdjusting(); + if (IsSaveAndRestartNeeded()) + { + restartNeeded = true; + PopupRestart(); + } + break; + } + } + else + { + switch(bp.GetEvent()) + { + case evTabControl: + case evTabPrint: + case evTabMsg: + case evTabSetup: + StopAdjusting(); + DelayTouchLong(); // by default, ignore further touches for a long time + TouchBeep(); + { + ButtonBase *btn = bp.GetButton(); + if (ChangePage(btn)) + { + currentButton.Clear(); // keep the button highlighted after it is released + } + } + break; + + case evSetBaudRate: + case evSetVolume: + case evSetColours: + case evSetLanguage: + case evCalTouch: + case evInvertX: + case evInvertY: + case evSaveSettings: + case evFactoryReset: + case evRestart: + // On the Setup tab, we allow any other button to be pressed to exit the current popup + StopAdjusting(); + DelayTouchLong(); // by default, ignore further touches for a long time + TouchBeep(); + mgr.ClearPopup(); + ProcessTouch(bp); + break; + + default: + break; + } + } + } + + // This is called when a button press times out + void OnButtonPressTimeout() + { + if (currentButton.IsValid()) + { + CurrentButtonReleased(); + } + } + + void UpdateFilesListTitle(int cardNumber, unsigned int numVolumes, bool isFilesList) + { + filePopupTitleField->SetValue(cardNumber); + filePopupTitleField->Show(isFilesList); + macroPopupTitleField->Show(!isFilesList); + changeCardButton->Show(isFilesList && numVolumes > 1); + filesUpButton->SetEvent((isFilesList) ? evFilesUp : evMacrosUp, nullptr); + mgr.SetPopup(fileListPopup, AutoPlace, AutoPlace); + } + + void FileListLoaded(int errCode) + { + if (errCode == 0) + { + mgr.Show(fileListErrorField, false); + } + else + { + fileListErrorField->SetValue(errCode); + mgr.Show(fileListErrorField, true); + } + } + + void EnableFileNavButtons(bool scrollEarlier, bool scrollLater, bool parentDir) + { + mgr.Show(scrollFilesLeftButton, scrollEarlier); + mgr.Show(scrollFilesRightButton, scrollLater); + mgr.Show(filesUpButton, parentDir); + } + + unsigned int GetNumScrolledFiles() + { + return numFileRows; + } + + void SetNumTools(unsigned int n) + { + // Tool button 0 is the bed, hence we use <= instead of < in the following + for (size_t i = 1; i < maxHeaters; ++i) + { + mgr.Show(toolButtons[i], i <= n); + } + } + + void SetBabystepOffset(float f) + { + babystepOffsetField->SetValue(f); + } +} + +#endif + +// End diff --git a/Src/main.c b/Src/main.c index 18e1617..c1ea7b1 100644 --- a/Src/main.c +++ b/Src/main.c @@ -66,7 +66,7 @@ QueueHandle_t xUIEventQueue; QueueHandle_t xPCommEventQueue; #define MAXCOMM1SIZE 0xffu // Biggest string the user will type -static uint8_t comm1RxBuffer = '\000'; // where we store that one character that just came in +uint8_t comm1RxBuffer = '\000'; // where we store that one character that just came in static volatile uint8_t comm1RxString[MAXCOMM1SIZE + 1]; // where we build our string from characters coming in static volatile int comm1RxIndex = 0; // index for going though comm1RxString @@ -612,37 +612,6 @@ void StartComm2Task(void const * argument) { } } -void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) -{ - if (huart->Instance == USART2) - { - __HAL_UART_FLUSH_DRREGISTER(&huart2); // Clear the buffer to prevent overrun -/* - int i = 0; - - if (comm1RxBuffer == '\n' || comm1RxBuffer == '\r') // If Enter - { - comm1RxString[comm1RxIndex] = 0; - comm1RxIndex = 0; - -// strncpy(comm1RxBuf, comm1RxString, MAX_STRING_SIZE); - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xSemaphoreGiveFromISR(xComm1Semaphore, &xHigherPriorityTaskWoken); - } - else - { - comm1RxString[comm1RxIndex] = comm1RxBuffer; // Add that character to the string - comm1RxIndex++; - - if (comm1RxIndex >= MAX_STRING_SIZE) // User typing too much, we can't have commands that big - { - comm1RxIndex = 0; - for (i = 0; i < MAX_STRING_SIZE; i++) comm1RxString[i] = 0; // Clear the string buffer - } - } -*/ - } -} /* USER CODE END 4 */ /* StartUITask function */ diff --git a/Src/serial_io.c b/Src/serial_io.c deleted file mode 100644 index 3e9e6f6..0000000 --- a/Src/serial_io.c +++ /dev/null @@ -1,76 +0,0 @@ - -#include -#include "stm32f1xx_hal.h" - -static uint16_t numChars = 0; -static uint8_t checksum = 0; -static unsigned int lineNumber = 0; - -static void rawSendChar(char c); -static void sendChar(char c); -static void sendCharAndChecksum(char c); - -extern UART_HandleTypeDef huart2; - -static void rawSendChar(char c) -{ - HAL_UART_Transmit(&huart2, &c, 1, 1000); -} - -static void sendCharAndChecksum(char c) -{ - checksum ^= c; - rawSendChar(c); - ++numChars; -} - -void sendInt(int i) -{ - if (i < 0) - { - sendChar('-'); - i = -i; - } - if (i >= 10) - { - sendInt(i/10); - i %= 10; - } - sendChar((char)((char)i + '0')); -} - -static void sendChar(char c) -{ - if (c == '\n') - { - if (numChars != 0) - { - // Send the checksum - rawSendChar('*'); - char digit0 = checksum % 10 + '0'; - checksum /= 10; - char digit1 = checksum % 10 + '0'; - checksum /= 10; - if (checksum != 0) - { - rawSendChar(checksum + '0'); - } - rawSendChar(digit1); - rawSendChar(digit0); - } - rawSendChar(c); - numChars = 0; - } - else - { - if (numChars == 0) - { - checksum = 0; - // Send a dummy line number - sendCharAndChecksum('N'); - sendInt(lineNumber++); // numChars is no longer zero, so only recurses once - sendCharAndChecksum(' '); - } - sendCharAndChecksum(c); - } -}