import processing.core.*; 
import processing.data.*; 
import processing.event.*; 
import processing.opengl.*; 

import java.awt.*; 
import java.awt.event.*; 
import java.awt.datatransfer.*; 
import javax.swing.*; 
import java.io.*; 
import java.util.Hashtable; 
import java.util.HashSet; 
import java.util.Set; 
import java.util.Vector; 
import java.util.Stack; 
import java.util.List; 
import java.util.regex.*; 

import java.util.HashMap; 
import java.util.ArrayList; 
import java.io.File; 
import java.io.BufferedReader; 
import java.io.PrintWriter; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.io.IOException; 

public class LEDmePlayDraw_270117 extends PApplet {

// TODO:
// Luxus: Textausgabe formatieren: die gr\u00f6\u00dften Zahlen bestimmen \u00fcber die Zahl der Leerzeichen zwischen den Zahlen
// Luxus: Screenshot des LED Panels exportieren: dann wei\u00df man, wie es mit den gew\u00e4hlten Farben ausgesehen hat
// Farben werden beim Exportieren eine Nuance dunkler (Abhilfe: in exportData() anstatt (int) (red(col)) round(red(col)) benutzt
// irgendwo wurde das noch nicht gemacht. einige farben werden immer noch eine nuance dunkler
// Erm\u00f6glichen, Bildteile auszuschneiden und getrennt zu sichern
// Am Bildschirm angezeigten Farbton auf Anzeige auf dem LED-Matrix-Panel abstimmen. Dazu m\u00fcssen die angezeigten Farbt\u00f6ne
//   und (R, G, B)-Wert getrennt abgelegt werden
// Unerw\u00fcnscht: Dr\u00fcckt man rechts von einer Farbpalette, wird die Farbe ganz rechts zur aktuellen Farbe erkl\u00e4rt
// Dateinamen beim Speichern sinnvoller vergeben: Pr\u00fcfen, ob man *.txt-Suffix angegeben hat
// Beim Laden muss gepr\u00fcft werden, ob eine Datei mit dem angegebenen Dateinamen existiert. Anderenfalls muss abgebrochen werden

// DONE:

// OK: Malen mit gedr\u00fcckter Maustaste
// OK: Indizierte Farbpalette zusammen mit dem Bild exportieren
// OK:Textausgabe vereinfachen: aus den indizierten Farben die kleinstm\u00f6glichen Zahlen machen!
// OK: RGB-Farben im ausgegebenen Kommentar als Color333, Color444 und Color888 ausdruecken 
// OK: Fl\u00e4chenf\u00fcllwerkzeug
// OK: Ich kann fuer die Farbkanaele einen unterschiedlichen Wertebereich festlegen: https://processing.org/tutorials/color/
// OK: Gelegentlich wird nach Ausf\u00fchren von Line das F\u00fcllen zur\u00fcckgenommen
// OK: \u00dcbrige Buttons objektorientiert machen, deren Update sauber machen
// OK: "Color2" wird dick gemalt und sieht dann bl\u00f6d aus
// OK: // Box: Zieht man eine Box/einen Kreis/eine Linie \u00fcber den Rand hinaus und macht dann mit der rechten Maustaste eine Box innerhalb dieser Box, verschwindet die \u00e4u\u00dfere Box
// OK: In Palette tauchen manchmal ganz oben zwei schwarze Boxen als Artefakte auf
// OK: Rand und Schrift der Dialogboxen sind anfangs schwarz, danach werden sie grau. Sie sollen schwarz bleiben
// OK: F\u00fcllt man das LED Paneel ohne Begrenzung, wird der verwendete Farbindex in die Textausgabe \u00fcbernommen und nicht heruntergerechnet
// NEIN: Zeichenwerkzeuge: Focus durch roten Kasten deutlich machen
// NEIN: Soll es m\u00f6glich sein, Box \u00fcber den Rand hinauszuziehen, um "offene" Boxes zu zeichnen?
// OK: Dialog jeweils neben Farbe1 und Farbe2: Pfeil hoch, Pfeil runter, Zahlenwert zwischen 0 und 15 in der Mitte je Farbkanal
// OK: \u00c4ndert sich die Farbe, wird sie w\u00e4hrend einer Punktop. nicht im LED Paneel aktualisiert
// OK: // Hintergrundfarbe ("LED aus") darf nicht \u00fcberschrieben werden k\u00f6nnen
// OK: Vollstaendige Farbpalette in Klartext exportieren
// OK: Der angegebene Wert je Farbkanal ist um 1 geringer als der echte Wert (Abhilfe: round(farbkanal-wert)
// OK: nach laden der palette wird diese aktualisiert, aber nicht angezeigt (Abhilfe: warten, bis Datei geladen ist)
// OK: Nach Laden der Palette werden die ColorManipulator-Objekte nicht oder erst nach Sekunden neu gemalt (Abhilfe: in draw() \u00fcber ein Flag bei Bedarf neu zeichnen)
// OK: Farben werden beim Exportieren eine Nuance dunkler (Abhilfe: in exportData() anstatt (int) (red(col)) round(red(col)) benutzt
// OK: Exportiertes Bild wieder laden
// OK: // Logikfehler beim Exportieren und Importieren: Ich kann ein Bild mit seiner Farbpalette nicht einfach wieder importieren, da
//    die Indices im Bild ver\u00e4ndert worden sind. Ich sollte daher zwei Dateien exportieren - eine mit unver\u00e4nderten Indices und Palette zum Reimport,
//    eine zweite Datei mit den ge\u00e4nderten Indices und der Palette der im Bild verwendeten Indices zum Einbetten in Arduino-Code
//    Bei einem leeren Bild wird als einziger Farbindex 1 angegeben, richtig w\u00e4re aber 0. Da wei\u00df zu Index 1 geh\u00f6rt, sind nach dem Laden diese 
//    Punkte dann weiss
// OK: Load-Routine so aendern, dass nur Reimport gelesen wird. Load-Routine robust machen. Das Programm friert ein, sobald dort ein Fehler geschieht
// UNNOETIG: Ggf. Farbverlaeufe zwischen linker und rechter Farbe je Reihe per Knopfdruck schaffen. Alternative: alle 4096 Color444-Farben anbieten
// UNNOETIG: Anschauen: http://www.colortools.net/color_picker_extended.html
// UNNOETIG: Farben modifizieren, Color333, Color444, Color888 (ist eigentlich Color878, Farben modifizieren, Farbbereich auf grelle Farben beschraenken
// OK: Fuer den sinnvollen Farbbereich die Adafruit-Demos fuer das LED Display ansehen
// ColorManipulator Objekt veraendert die Paletten nicht mehr, wahrscheinlich wird palette.repaint() aufgerufen, worin die Palette nicht mehr neu
//    gezeichnet wird
// OK: Dynamische Palette wird bei Veraenderung des Blauanteils nicht mehr neu gezeichnet (Abhilfe: anstelle palette.repaint() palette.draw...() verwendet)
// OK: \u00dcber der Palette die Farbkan\u00e4le und den Index der Farbe anzeigen
// OK: LEDoff-Schattierung (2, 2, 2) aus linker Palette herausnehmen. 
// OK: Farben sind nicht gleichmaessig auf die vier Paletten verteilt 
// OK: Ver\u00e4ndert man Farben in der 64 Color-Palette, werden diese nicht ver\u00e4ndert dargestellt
// OK: In den ersten beiden Reihen links in den linken Paletten kann man keine Farbe ausw\u00e4hlen bzw. die aktuelle Farbe wird nicht auf diese aktualisiert
// OK: Separate Farbpalette ganz rechts, die mit LEDOff-Schattierung (2, 2, 2) beginnt
// OK: Farbpalette wird nach dem Laden nicht aktualisiert (Abhilfe: Flags "palette.repaintUpperLeftColorBox = true;" usw. in "if loadButton != null) {}" gesetzt
// OK: Exportieren: das Format f\u00fcr die Farben vereinfachen: anstatt Color444(3, 3, 3) einfach die Tripel durch Kommata trennen (3, 3, 3, 5, 6, 3, 2, 3, 4, ...)
// OK: Die Botschaft "Operation pending" erscheint beim Laden nicht
// OK: Links oben werden kleine Rechtecke gemalt (Abhilfe: Nur falls alte Position nicht neuer Position entspricht, dann alte Box l\u00f6schen)
// OK: Die Schrift "ColorX" und "RGB" in ColorManipulator1 und 2 sind klecksig (Abhilfe: das geschieht, wenn man die Schrift einfach mit der gleichen Schrift \u00fcbermalt. Es hilft, vorher den Untergrund zu l\u00f6schen)
// OK: Beim \u00dcbermalen der LED-Punkte entstehen Artefakte, \u00e4hnlich wie bei der Schrift (gel\u00f6st wie bei der Schrift)
// OK: Beim Exportieren File Requester f\u00fcr den Programmnamen anbieten
// OK: Nach dem Speichern malt man Kreise oder Rechtecke mit ledOff (Abhilfe: drawingMode nach Ladevorgang auf mPOINT gesetzt)
// UNNOETIG: CurrentColor in Farbpalette kenntlich machen (Ich mache das jetzt immerhin textuell)
// OK: Bei Redo wird die Farbe der LEDs schwarz (Abhilfe: das buffer-Array in Undo wird jetzt mit ledOffIndex, nicht mit 0 initialisiert)
// OK: Undo-Funktionalit\u00e4t erg\u00e4nzen
// OK: Undo-Verhalten implementieren: if the user made alterations to current state that is not the newest state, erase all ongoing states

 // Needed for clipboard functionality












final int mPOINT = 0;
final int mBOX = 1;
final int mLINE = 2;
final int mCIRCLE = 3;
final int mFILL = 4;

final int RED = 0;
final int GREEN = 1;
final int BLUE = 2;

final int ledOffIndex = 4864;

final int UNDO_STEPS = 1000;

int drawingMode;
LineTool lineTool;
PointTool pointTool;
BoxTool boxTool;
CircleTool circleTool;
FillTool fillTool;
ToolGroup toolGroup = new ToolGroup();

ExportButton exportButton;
ClearButton clearButton;
LoadButton loadButton;
UndoButton undoButton;
RedoButton redoButton;

ColorManipulator color1Manipulator;
ColorManipulator color2Manipulator;

ColorRGB444Palette palette;

UndoBuffer undoHistory;

// This program uses an indexed color palette with 4609 entries.
// The number is arbitrary, though.
public int[] indexColor = new int[4096+768+64+1];
public PFont f;
public int currentLED_x;
public int currentLED_y;
public int formerLED_x;
public int formerLED_y;
public int[][] frameBuffer = new int[32][32]; // Contain the color indices placed into the LEDmePlay framebuffer
public int[][] backBuffer = new int[32][32]; // Backup buffer for drawing operations
public int foregroundCol;
public int backgroundCol;
public int contourCol;
public int black;
public int white;
public int grey;
public int red;
public boolean useColor1 = true;
public boolean useColor2 = false;
public int dotDiameter = 13;
public int indexColor1 = 255;
public int indexColor2 = ledOffIndex;
public int currentColor_x;
public int currentColor_y;
public int formerColor_x;
public int formerColor_y;
public int currentColorIndex;

public boolean diskOperationPending = false;

public int defaultBlue = 15; // Value of RGB channel blue in the lower right palette ("Page" between 0..15)

public int startX;
public int startY;
public int endX;
public int endY;
public boolean dragging;
public boolean fileLoaded = false;

public void setup() 
{
  
  colorMode(RGB, 16, 16, 16); // Turn on RGB color mode 4/4/4 (2^4 = 16)
  background(6);
  
  
  red = color(13, 2, 2);
  white = color(15, 15, 15);
  black = color(0, 0, 0);
  grey = color(9, 9, 9);
  contourCol = color(0, 0, 0);
  
  clearLEDmePlayFrameBuffer();
  
  palette = new ColorRGB444Palette(960, 45, 875, 629, indexColor, defaultBlue);
  drawEmptyMatrix();
  
  foregroundCol = indexColor[indexColor1];
  backgroundCol = indexColor[indexColor2];
  
  pointTool = new PointTool(1000, 840, 50, 50);
  pointTool.setToolGroup(toolGroup);
  pointTool.select(); drawingMode = mPOINT;
  lineTool = new LineTool(1140, 840, 50, 50);
  lineTool.setToolGroup(toolGroup);
  circleTool = new CircleTool(1210, 840, 50, 50);
  circleTool.setToolGroup(toolGroup);
  boxTool = new BoxTool(1070, 840, 50, 50);
  boxTool.setToolGroup(toolGroup);
  fillTool = new FillTool(1280, 840, 50, 50);
  fillTool.setToolGroup(toolGroup);
  
  clearButton = new ClearButton(1350, 840, 175, 50);
  exportButton = new ExportButton(1600, 840, 105, 50);
  loadButton = new LoadButton(1720, 840, 85, 50);
  undoButton = new UndoButton(1000, 907, 90, 50);
  redoButton = new RedoButton(1110, 907, 90, 50);
  
  color1Manipulator = new ColorManipulator(995, 715, indexColor, indexColor1, "Color1");
  color2Manipulator = new ColorManipulator(1355, 715, indexColor, indexColor2, "Color2");
  color2Manipulator.deactivated = true; // Is set to ledOffColor which must not be altered
  color2Manipulator.repaint();
  
  undoHistory = new UndoBuffer(UNDO_STEPS); // Create an undo/redo buffer with UNDO_STEPS steps/states
  
  f = loadFont("ArialMT-30.vlw");
  if (f == null) { f = loadFont("ArialMT-30.vlw"); }
  textFont(f, 30);
   
  formerLED_x = 1;
  formerLED_y = 1;
  currentLED_x = 0;
  currentLED_y = 0;
  
  currentColor_x = 0;
  currentColor_y = 0;
  formerColor_x = -50;
  formerColor_y = -50;
}

public void draw() 
{
  int colIndex = 0;
  
  if (diskOperationPending)
  {
    // Inform user that we are busy
    fill(0);
    textFont(f,30);
    fill(6, 6, 6);
    noStroke();
    rect(39, 920, 900, 40);
    fill(black);
    text("Please wait - disk operation pending...", 40, 950);
    
    return;
  }
  
  // Mouse is over the LED matrix panel
  if ((mouseX > 36) && (mouseX < 895) && (mouseY > 36) && (mouseY < 895))
  {
    if (drawingMode == mPOINT)
    {
      if (mousePressed == true)
      {
        backupFrameBuffer();
        
        currentLED_x = (mouseX - 40) / 27;
        currentLED_y = (mouseY - 40) / 27;
        noStroke();
        
        if (mouseButton == LEFT) 
        { 
          fill(indexColor[indexColor1]); 
          frameBuffer[currentLED_x][currentLED_y] = indexColor1;
        } 
        else if (mouseButton == RIGHT)
        { 
          fill(indexColor[indexColor2]); 
          frameBuffer[currentLED_x][currentLED_y] = indexColor2;
        }
        
        ellipse(50 + currentLED_x * 27, 50 + currentLED_y * 27, dotDiameter, dotDiameter);
      }
    } // mPOINT
    else if (drawingMode == mLINE)
    {
      if (mousePressed == true)
      {
        restoreFrameBuffer();
       
        if (mouseButton == LEFT) { colIndex = indexColor1; } else { colIndex = indexColor2; };
        if (!dragging)
        {
          startX = mouseX;
          startY = mouseY;
          dragging = true;
        }
        else
        {
          backupFrameBuffer();
          endX = mouseX;
          endY = mouseY;
          drawLEDLine(startX, startY, endX, endY, colIndex);
        }
      }
    } // mLINE
    else if (drawingMode == mBOX)
    {
      if (mousePressed == true)
      {
        restoreFrameBuffer();
        
        if (mouseButton == LEFT) { colIndex = indexColor1; } else { colIndex = indexColor2; };
        if (!dragging)
        {
          startX = mouseX;
          startY = mouseY;
          dragging = true;
        }
        else
        {
          backupFrameBuffer();
          
          endX = mouseX;
          endY = mouseY;
          stroke(white);
          strokeWeight(1);
          noFill();
          drawLEDBox(startX, startY, endX, endY, colIndex);
        }
      }
    } // mBOX
     else if (drawingMode == mCIRCLE)
    {
      if (mousePressed == true)
      {
        restoreFrameBuffer();
        
        if (mouseButton == LEFT) { colIndex = indexColor1; } else { colIndex = indexColor2; };
        if (!dragging)
        {
          startX = mouseX;
          startY = mouseY;
          dragging = true;
        }
        else
        {
          backupFrameBuffer();
          
          endX = mouseX;
          endY = mouseY;
          stroke(white);
          strokeWeight(1);
          noFill();
          drawLEDCircle(startX, startY, endX, endY, colIndex);
        }
      }
    } // mCIRCLE
    else if (drawingMode == mFILL) // Uses the color of the LED under the mouse cursor as background color, every other color as boundary
    {
      if (mousePressed == true)
      {
        backupFrameBuffer();
        
        currentLED_x = (mouseX - 40) / 27;
        currentLED_y = (mouseY - 40) / 27;
        noStroke();
        
        if (mouseButton == LEFT) { colIndex = indexColor1; } else { colIndex = indexColor2; };
        fillArea(currentLED_x, currentLED_y, colIndex);
      }
    } // mFILL
  }
  
  if (fileLoaded == true) // I file has been loaded. The GUI needs to reflect potential changes in the color palettes.
  { 
    fileLoaded = false; 
    
    indexColor[0] = ledOffIndex;
    if (color1Manipulator != null) { color1Manipulator.setNewColorIndex(indexColor1); color1Manipulator.repaint(); }; 
    if (color2Manipulator != null) { color2Manipulator.setNewColorIndex(indexColor2); color2Manipulator.repaint(); };
    
    palette.repaintUpperLeftColorBox = true;
    palette.repaintUpperRightColorBox = true;
    palette.repaintLowerLeftColorBox = true;
    palette.repaintDynamicColorBox = true;
    palette.repaint64ColorBox = true;
    palette.repaint();
    
    pointTool.select();
    drawingMode = mPOINT;
    drawEmptyMatrix();
    drawFrameBuffer();
  }
}

// Performs an iterative 4-neighborship flood fill starting at the LED at grid position (x, y)
// Uses the color at (x, y) as background color, any other color as boundary
// The algorithm is faulty, as it does not terminate when a boundary is lacking
public void fillArea(int x, int y, int colIndex)
{
  Stack<Point> stack = new Stack<Point>();
  int bgColIndex = frameBuffer[x][y];
  stack.add((Point) new Point(x, y));

  while ((!stack.empty()) && (stack.size() < 8192)) // The latter is for safety, in case of an algorithmic error
  {
    Point p = stack.pop();
    if (frameBuffer[p.x][p.y] == bgColIndex)
    {
      // Color with new color
      frameBuffer[p.x][p.y] = colIndex;
      // Visit neighbors
      if ((p.x >= 0) && (p.y-1 >= 0) && (p.x < 32) && (p.y-1 < 32)) stack.push(new Point(p.x, p.y-1));
      if ((p.x-1 >= 0) && (p.y >= 0) && (p.x-1 < 32) && (p.y < 32)) stack.push(new Point(p.x-1, p.y));
      if ((p.x+1 >= 0) && (p.y >= 0) && (p.x+1 < 32) && (p.y < 32)) stack.push(new Point(p.x+1, p.y));
      if ((p.x >= 0) && (p.y+1 >= 0) && (p.x < 32) && (p.y+1 < 32)) stack.push(new Point(p.x, p.y+1));
    }
  }
  drawFrameBuffer();
}

public void backupFrameBuffer()
{
  for (int x = 0; x < 32; x++)
  {
    for (int y = 0; y < 32; y++)
    {
      backBuffer[x][y] = frameBuffer[x][y];
    }
  }
}

public void restoreFrameBuffer()
{
  noStroke();
  
  for (int x = 0; x < 32; x++)
  {
    for (int y = 0; y < 32; y++)
    {
      frameBuffer[x][y] = backBuffer[x][y];
      fill(indexColor[frameBuffer[x][y]]);
      ellipse(50 + x * 27, 50 + y * 27, dotDiameter, dotDiameter);  
    }
  }
}

public void drawFrameBuffer()
{
  noStroke();
  for (int x = 0; x < 32; x++)
  {
    for (int y = 0; y < 32; y++)
    {
      fill(0);
      ellipse(50 + x * 27, 50 + y * 27, dotDiameter+1, dotDiameter+1);  
      fill(indexColor[frameBuffer[x][y]]);
      ellipse(50 + x * 27, 50 + y * 27, dotDiameter, dotDiameter);  
    }
  }
}

// Not currently used
public void makeRGB444Gradient(int fromIndex, int toIndex)
{
  int numSteps = abs(fromIndex - toIndex);
  int fromColor = indexColor[fromIndex];
  int toColor = indexColor[toIndex];
  float redFraction = (red(toColor) - red(fromColor)) / numSteps;
  float greenFraction = (green(toColor) - green(fromColor)) / numSteps;
  float blueFraction = (blue(toColor) - blue(fromColor)) / numSteps;
  println("red, green blue fraction: "+redFraction+", "+greenFraction+", "+blueFraction);
  
  int col = indexColor[fromIndex];
  int step = 1;
  for (int index = fromIndex+1; index < toIndex; index++)
  {
    indexColor[index] = color((float) (red(col)+step*redFraction), (float) (green(col)+step*greenFraction), (float) (blue(col)+step*blueFraction));
    step++;
  }
}

public void drawLEDLine(int fromX, int fromY, int toX, int toY, int colIndex)
{
  // Uses a Processing implementation of the "compact" variant of Bresenham's line drawing algorithm written in C,
  // German Wikipedia article https://de.wikipedia.org/wiki/Bresenham-Algorithmus#Kompakte_Variante, January 15, 2017
  
  fill(indexColor[colIndex]);
  noStroke();
  
  int x0 = (fromX - 40) / 27;
  int y0 = (fromY - 40) / 27;
  int x1 = (toX - 40) / 27;
  int y1 = (toY - 40) / 27;
  int dx =  abs(x1-x0), sx = x0<x1 ? 1 : -1;
  int dy = -abs(y1-y0), sy = y0<y1 ? 1 : -1;
  int err = dx+dy, e2; // error value e_xy
  
  while(true)
  {
    ellipse(50 + x0 * 27, 50 + y0 * 27, dotDiameter, dotDiameter);
    frameBuffer[x0][y0] = colIndex;
    if (x0==x1 && y0==y1) break;
    e2 = 2*err;
    if (e2 > dy) { err += dy; x0 += sx; } // e_xy+e_x > 0
    if (e2 < dx) { err += dx; y0 += sy; } // e_xy+e_y < 0
  }
}

public void drawLEDBox(int startX, int startY, int endX, int endY, int colIndex)
{
  drawLEDLine(startX, startY, endX, startY, colIndex);
  drawLEDLine(startX, startY, startX, endY, colIndex);
  drawLEDLine(startX, endY, endX, endY, colIndex);
  drawLEDLine(endX, startY, endX, endY, colIndex);
}

// Draws a circle with center (x0, y0) in the LED panel coordinate system,
// its radius defined by the size of the mouse drag operation
public void drawLEDCircle(int fromX, int fromY, int toX, int toY, int colIndex)
{
  // Uses a Processing implementation of the circle drawing algorithm in C from the German Wikipedia
  // article https://de.wikipedia.org/wiki/Bresenham-Algorithmus#Kompakte_Variante, January 15, 2017
  
  fill(indexColor[colIndex]);
  noStroke();
  
  int x0 = (fromX - 40) / 27;
  int y0 = (fromY - 40) / 27;
  int x1 = (toX - 40) / 27;
  int y1 = (toY - 40) / 27;
  
  int radius = floor(sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)));
  
  int f = 1 - radius;
  int ddF_x = 0;
  int ddF_y = -2 * radius;
  int x = 0;
  int y = radius;
    
  setPixel(x0, y0 + radius, colIndex);
  setPixel(x0, y0 - radius, colIndex);
  setPixel(x0 + radius, y0, colIndex);
  setPixel(x0 - radius, y0, colIndex);

  while(x < y)
  {
    if(f >= 0)
    {
      y--;
      ddF_y += 2;
      f += ddF_y;
    }
    x++;
    ddF_x += 2;
    f += ddF_x + 1;

    setPixel(x0 + x, y0 + y, colIndex);
    setPixel(x0 - x, y0 + y, colIndex);
    setPixel(x0 + x, y0 - y, colIndex);
    setPixel(x0 - x, y0 - y, colIndex);
    setPixel(x0 + y, y0 + x, colIndex);
    setPixel(x0 - y, y0 + x, colIndex);
    setPixel(x0 + y, y0 - x, colIndex);
    setPixel(x0 - y, y0 - x, colIndex);
  }
}

/*
// Alternate variant - constructs circle within a bounding box
// Does not work reliably, as the midpoint of the circle jumps due to the low resolution
void drawLEDCircle(int fromX, int fromY, int toX, int toY, int colIndex)
{
  // Processing implementation, modified from the circle drawing algorithm in C from the German Wikipedia
  // article https://de.wikipedia.org/wiki/Bresenham-Algorithmus#Kompakte_Variante, January 15, 2017
  
  fill(indexColor[colIndex]);
  noStroke();
  
  int x0 = (fromX - 40) / 27;
  int y0 = (fromY - 40) / 27;
  int x1 = (toX - 40) / 27;
  int y1 = (toY - 40) / 27;
  
  // The circle shall be constructed such that fromX, fromY is the upper left point, toX, toY the lower left point
  // The radius shall, therefore, be half the distance between these points
  int radius = (int) (0.5 * (sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0))));
  // Move (x0, y0) such that it is the midpoint of the line between (x0, y0) and (x1, y1)
  int mx = (fromX + toX)/2; // (mx, my) is the midpoint
  int my = (fromX + toX)/2;
  int nx = ((mx - 40) / 27); // The midpoint needs to be normalized at the LED panel raster
  int ny = ((my - 40) / 27);
  
  int f = 1 - radius;
  int ddF_x = 0;
  int ddF_y = -2 * radius;
  int x = 0;
  int y = radius;
    
  setPixel(nx, ny + radius, colIndex);
  setPixel(nx, ny - radius, colIndex);
  setPixel(nx + radius, ny, colIndex);
  setPixel(nx - radius, ny, colIndex);

  while(x < y)
  {
    if(f >= 0)
    {
      y--;
      ddF_y += 2;
      f += ddF_y;
    }
    x++;
    ddF_x += 2;
    f += ddF_x + 1;

    setPixel(nx + x, ny + y, colIndex);
    setPixel(nx - x, ny + y, colIndex);
    setPixel(nx + x, ny - y, colIndex);
    setPixel(nx - x, ny - y, colIndex);
    setPixel(nx + y, ny + x, colIndex);
    setPixel(nx - y, ny + x, colIndex);
    setPixel(nx + y, ny - x, colIndex);
    setPixel(nx - y, ny - x, colIndex);
  }
}
*/

public void setPixel(int x, int y, int colIndex)
{
  if ((50+x*27 > 29) && (50+x*27 < 33*27) && (50+y*27 > 29) && (50+y*27 < 33*27))
  {
    ellipse(50 + x * 27, 50 + y * 27, dotDiameter, dotDiameter);
    if ((x >= 0) && (x <= 31) && (y >= 0) && (y <= 31)) { frameBuffer[x][y] = colIndex; }
  }
}

// Draws a simplified representation of the front of the LED matrix panel
public void drawEmptyMatrix()
{
  noStroke();
  strokeWeight(1);
  fill(1);
  rect(27, 27, 32*27+20, 32*27+20);
  fill(indexColor[ledOffIndex]);
  for (int x = 0; x < 32*27; x = x + 27  )
  {
    for (int y = 0; y < 32*27; y = y + 27)
    {
      ellipse(50+x, 50+y, dotDiameter, dotDiameter);     
    }
  }
}

// Clears the LEDmePlay's frame buffer, also empties the backup buffer
public void clearLEDmePlayFrameBuffer()
{
  for (int x=0; x < 32; x++)
  {
    for (int y = 0; y < 32; y++)
    {
      frameBuffer[x][y] = ledOffIndex;
      backBuffer[x][y] = ledOffIndex;
    }
  }
}

public void clearMarkings()
{
  // Clear the text beneath the LED matrix panel
  fill(6, 6, 6);
  noStroke();
  rect(39, 920, 900, 40);
    
  // Clear former LED
  noStroke();
  int colIndex = frameBuffer[formerLED_x][formerLED_y];
  int col = indexColor[colIndex];
  fill(col);
  ellipse(50 + formerLED_x * 27, 50 + formerLED_y * 27, dotDiameter, dotDiameter);
  
  palette.repaint();
}

public void mouseMoved()
{
  // If the mouse is over the LED matrix panel, highlight the respective LED
  if ((mouseX > 36) && (mouseX < 895) && (mouseY > 36) && (mouseY < 895))
  {
      // Clear former LED
      noStroke();
      strokeWeight(1);
      int colIndex = frameBuffer[formerLED_x][formerLED_y];
      int col = indexColor[colIndex];
      fill(col);
      ellipse(50 + formerLED_x * 27, 50 + formerLED_y * 27, dotDiameter, dotDiameter);
      
      currentLED_x = (mouseX - 40) / 27;
      currentLED_y = (mouseY - 40) / 27;
      
      // Highlight the LED dot the mouse is currently over
      noStroke();
      fill(13, 13, 13);
      ellipse(50 + currentLED_x * 27, 50 + currentLED_y * 27, dotDiameter, dotDiameter);
      
      formerLED_x = currentLED_x;
      formerLED_y = currentLED_y;
    
   
    if (!diskOperationPending)
    {
      // Draw current LEDmePlay coordinates and color index
      fill(0);
      textFont(f,30);
      fill(6, 6, 6);
      noStroke();
      rect(39, 920, 900, 40);
      fill(black);
      String s;
      if (frameBuffer[currentLED_x][currentLED_y] != ledOffIndex)
      {
        s = "(" + currentLED_x + ", " + currentLED_y + ") = " + frameBuffer[currentLED_x][currentLED_y] + ": RGB444 color (";
        int c = indexColor[frameBuffer[currentLED_x][currentLED_y]];
        s += round(red(c)) + ", "+ round(green(c)) + ", "+ round(blue(c)) + ")";
      }
      else
      {
        s = "(" + currentLED_x + ", " + currentLED_y + ") = LED off";
      }
      text(s, 40, 950);
    }
  }
  else
  {
    clearMarkings();
  }
  
  if ((exportButton != null) && ((!exportButton.mouseOver(mouseX, mouseY)) && (exportButton.hasFocus))) { exportButton.hasFocus = false; exportButton.pressed = false; exportButton.repaint(); }
  if ((clearButton != null) && ((!clearButton.mouseOver(mouseX, mouseY)) && (clearButton.hasFocus))) { clearButton.hasFocus = false; clearButton.pressed = false; clearButton.repaint(); }
  if ((loadButton != null) && ((!loadButton.mouseOver(mouseX, mouseY)) && (loadButton.hasFocus))) { loadButton.hasFocus = false; loadButton.pressed = false; loadButton.repaint(); }
  if ((undoButton != null) && ((!undoButton.mouseOver(mouseX, mouseY)) && (undoButton.hasFocus))) { undoButton.hasFocus = false; undoButton.pressed = false; undoButton.repaint(); }
  if ((redoButton != null) && ((!redoButton.mouseOver(mouseX, mouseY)) && (redoButton.hasFocus))) { redoButton.hasFocus = false; redoButton.pressed = false; redoButton.repaint(); }

  if ((palette != null) && ((palette.mouseOver(mouseX, mouseY)) && (!palette.hasFocus))) { palette.hasFocus = true; }
}

public void mousePressed() 
{
  // React only if the mouse is over the LED matrix panel
  if ((mouseX > 36) && (mouseX < 895) && (mouseY > 36) && (mouseY < 895))
  {
    if (mouseButton == LEFT)
    {
      useColor1 = true;
      useColor2 = false;
    }
    else if (mouseButton == RIGHT)
    {
      useColor2 = true;
      useColor1 = false;
    }
  }
  
  else if ((mouseX > 956) && (mouseX < 1900) && (mouseY > 40) && (mouseY < 684)) // Mouse is over the color palette
  {
    // The mouse is within the index color selector ("palette")
    if (mouseButton == LEFT) // user selected foreground color
    { 
      indexColor1 = currentColorIndex;
      color1Manipulator.setNewColorIndex(indexColor1);
      if (currentColorIndex == ledOffIndex) // Prohibit modifying background color ("LED off")
      { 
        color1Manipulator.deactivated = true; 
      }
      else
      {
        color1Manipulator.deactivated = false; 
      }
      color1Manipulator.repaint();
    }
    else // User selected background color
    {
      indexColor2 = currentColorIndex;
      color2Manipulator.setNewColorIndex(indexColor2);
      if (currentColorIndex == ledOffIndex) // Prohibit modifying background color ("LED off")
      { 
        color2Manipulator.deactivated = true; 
      }
      else
      {
        color2Manipulator.deactivated = false; 
      }
      color2Manipulator.repaint();
    }
  }
  
  if ((pointTool != null) && (pointTool.mouseOver(mouseX, mouseY))) { pointTool.select(); drawingMode = mPOINT; }
  if ((lineTool != null) && (lineTool.mouseOver(mouseX, mouseY))) { lineTool.select(); drawingMode = mLINE; }
  if ((boxTool != null) && (boxTool.mouseOver(mouseX, mouseY))) { boxTool.select(); drawingMode = mBOX; }
  if ((circleTool != null) && (circleTool.mouseOver(mouseX, mouseY))) { circleTool.select(); drawingMode = mCIRCLE; }
  if ((fillTool != null) && (fillTool.mouseOver(mouseX, mouseY))) { fillTool.select(); drawingMode = mFILL; }
  if ((exportButton != null) && (exportButton.mouseOver(mouseX, mouseY))) 
  { 
    exportButton.pressed = true; 
    exportButton.repaint();
    diskOperationPending = true;
    
    SwingUtilities.invokeLater(new Runnable() 
    {
      public void run() 
      {
        selectOutput("Select output file:", "outputFileSelected"); 
      }
    } );
   
    fileLoaded = true;
  }
  
  if ((clearButton != null) && (clearButton.mouseOver(mouseX, mouseY))) { clearButton.pressed = true; clearButton.repaint(); drawEmptyMatrix(); clearLEDmePlayFrameBuffer(); undoHistory.addState(); }
  if ((undoButton != null) && (undoButton.mouseOver(mouseX, mouseY))) { undoButton.pressed = true; undoButton.repaint(); undoHistory.goBack(); }
  if ((redoButton != null) && (redoButton.mouseOver(mouseX, mouseY))) { redoButton.pressed = true; redoButton.repaint(); undoHistory.goForward(); }
  
  if (color1Manipulator.getRedUp().mouseOver(mouseX, mouseY)) { color1Manipulator.getRedUp().push(); }
  if (color1Manipulator.getGreenUp().mouseOver(mouseX, mouseY)) { color1Manipulator.getGreenUp().push(); }
  if (color1Manipulator.getBlueUp().mouseOver(mouseX, mouseY)) { color1Manipulator.getBlueUp().push(); }
  if (color1Manipulator.getRedDown().mouseOver(mouseX, mouseY)) { color1Manipulator.getRedDown().push(); }
  if (color1Manipulator.getGreenDown().mouseOver(mouseX, mouseY)) { color1Manipulator.getGreenDown().push(); }
  if (color1Manipulator.getBlueDown().mouseOver(mouseX, mouseY)) { color1Manipulator.getBlueDown().push(); }
  
  if (color2Manipulator.getRedUp().mouseOver(mouseX, mouseY)) { color2Manipulator.getRedUp().push(); }
  if (color2Manipulator.getGreenUp().mouseOver(mouseX, mouseY)) { color2Manipulator.getGreenUp().push(); }
  if (color2Manipulator.getBlueUp().mouseOver(mouseX, mouseY)) { color2Manipulator.getBlueUp().push(); }
  if (color2Manipulator.getRedDown().mouseOver(mouseX, mouseY)) { color2Manipulator.getRedDown().push(); }
  if (color2Manipulator.getGreenDown().mouseOver(mouseX, mouseY)) { color2Manipulator.getGreenDown().push(); }
  if (color2Manipulator.getBlueDown().mouseOver(mouseX, mouseY)) { color2Manipulator.getBlueDown().push(); }
  
  if (palette.getManipulator().getUp().mouseOver(mouseX, mouseY)) { palette.getManipulator().getUp().push(); }
  if (palette.getManipulator().getDown().mouseOver(mouseX, mouseY)) { palette.getManipulator().getDown().push(); }
  
  if ((loadButton != null) && (loadButton.mouseOver(mouseX, mouseY))) 
  { 
    loadButton.pressed = true; 
    loadButton.repaint(); 
    diskOperationPending = true;
    SwingUtilities.invokeLater(new Runnable() 
    {
      public void run() 
      {
        selectInput("Select a file to load:", "inputFileSelected"); 
      }
    } );
    
    fileLoaded = true; 
  }
}

public void mouseReleased()
{
  dragging = false;
  startX = mouseX;
  startY = mouseY;
  endX = startX;
  endY = startY;
    
  if ((mouseX > 36) && (mouseX < 895) && (mouseY > 36) && (mouseY < 895)) // Mouse is over the LED matrix panel
  {   
    backupFrameBuffer();
    
    // Add the modified state of the image to the Undo history
    // If the user has modified an intermediate state, not the latest state, delete the history starting from that point in time
    if (undoHistory.currentState == undoHistory.statesUsed) { undoHistory.addState(); }
    else { undoHistory.replaceAsLast(); }
  }
  
  if ((undoButton != null) && ((undoButton.mouseOver(mouseX, mouseY)) && (undoButton.pressed))) 
  { 
    undoButton.hasFocus = false; 
    undoButton.pressed = false; 
    
    SwingUtilities.invokeLater(new Runnable() 
    {
      public void run() 
      {
        undoButton.repaint();
      }
    } );
  }
 
  if ((exportButton != null) && ((exportButton.mouseOver(mouseX, mouseY)) && (exportButton.pressed))) 
  { 
    exportButton.hasFocus = false; 
    exportButton.pressed = false; 
    SwingUtilities.invokeLater(new Runnable() 
    {
      public void run() 
      {
        exportButton.repaint(); 
      }
    } );
  }
  
  if ((clearButton != null) && ((!clearButton.mouseOver(mouseX, mouseY)) && (clearButton.pressed))) 
  { 
    clearButton.hasFocus = false; 
    clearButton.pressed = false; 
    SwingUtilities.invokeLater(new Runnable() 
    {
      public void run() 
      {
        clearButton.repaint(); 
      }
    } );
  }
  
  if ((loadButton != null) && ((!loadButton.mouseOver(mouseX, mouseY)) && (loadButton.pressed))) 
  { 
    loadButton.hasFocus = false; 
    loadButton.pressed = false; 
    SwingUtilities.invokeLater(new Runnable() 
    {
      public void run() 
      {
        loadButton.repaint(); 
      }
    } );
  }
  
  if ((redoButton != null) && ((!redoButton.mouseOver(mouseX, mouseY)) && (redoButton.pressed))) 
  {
    redoButton.hasFocus = false; 
    redoButton.pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         redoButton.repaint(); 
       }
     } );
   }
   
  // Color1 manipulators
  if ((color1Manipulator.getRedUp().mouseOver(mouseX, mouseY)) && (color1Manipulator.getRedUp().pressed)) 
  { 
    color1Manipulator.getRedUp().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         color1Manipulator.getRedUp().repaint(); 
       }
     } );
  }
  
  if ((color1Manipulator.getGreenUp().mouseOver(mouseX, mouseY)) && (color1Manipulator.getGreenUp().pressed)) 
  { 
    color1Manipulator.getGreenUp().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         color1Manipulator.getGreenUp().repaint(); 
       }
     } );
  }
  
  if ((color1Manipulator.getBlueUp().mouseOver(mouseX, mouseY)) && (color1Manipulator.getBlueUp().pressed)) 
  { 
    color1Manipulator.getBlueUp().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         color1Manipulator.getBlueUp().repaint(); 
       }
     } );
  }
  
  if ((color1Manipulator.getRedDown().mouseOver(mouseX, mouseY)) && (color1Manipulator.getRedDown().pressed)) 
  { 
    color1Manipulator.getRedDown().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         color1Manipulator.getRedDown().repaint(); 
       }
     } );
  }
  
  if ((color1Manipulator.getGreenDown().mouseOver(mouseX, mouseY)) && (color1Manipulator.getGreenDown().pressed)) 
  { 
    color1Manipulator.getGreenDown().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         color1Manipulator.getGreenDown().repaint(); 
       }
     } );
  }
  
  if ((color1Manipulator.getBlueDown().mouseOver(mouseX, mouseY)) && (color1Manipulator.getBlueDown().pressed)) 
  { 
    color1Manipulator.getBlueDown().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         color1Manipulator.getBlueDown().repaint(); 
       }
     } );
  }
  
  // Color2 manipulators
  if ((color2Manipulator.getRedUp().mouseOver(mouseX, mouseY)) && (color2Manipulator.getRedUp().pressed)) 
  { 
    color2Manipulator.getRedUp().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         color2Manipulator.getRedUp().repaint(); 
       }
     } );
  }
  
  if ((color2Manipulator.getGreenUp().mouseOver(mouseX, mouseY)) && (color2Manipulator.getGreenUp().pressed)) 
  { 
    color2Manipulator.getGreenUp().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         color2Manipulator.getGreenUp().repaint(); 
       }
     } );
  }
  
  if ((color2Manipulator.getBlueUp().mouseOver(mouseX, mouseY)) && (color2Manipulator.getBlueUp().pressed)) 
  { 
    color2Manipulator.getBlueUp().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         color2Manipulator.getBlueUp().repaint(); 
       }
     } );
  }
  
  if ((color2Manipulator.getRedDown().mouseOver(mouseX, mouseY)) && (color2Manipulator.getRedDown().pressed)) 
  { 
    color2Manipulator.getRedDown().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         color2Manipulator.getRedDown().repaint(); 
       }
     } );
  }
  
  if ((color2Manipulator.getGreenDown().mouseOver(mouseX, mouseY)) && (color2Manipulator.getGreenDown().pressed)) 
  { 
    color2Manipulator.getGreenDown().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         color2Manipulator.getGreenDown().repaint(); 
       }
     } );
  }
  
  if ((color2Manipulator.getBlueDown().mouseOver(mouseX, mouseY)) && (color2Manipulator.getBlueDown().pressed)) 
  { 
    color2Manipulator.getBlueDown().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         color2Manipulator.getBlueDown().repaint(); 
       }
     } );
  }
  
  if ((palette.getManipulator().getUp().mouseOver(mouseX, mouseY)) && (palette.getManipulator().getUp().pressed)) 
  { 
    palette.getManipulator().getUp().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         palette.getManipulator().getUp().repaint();
       }
     } );
  }
  
  if ((palette.getManipulator().getDown().mouseOver(mouseX, mouseY)) && (palette.getManipulator().getDown().pressed)) 
  { 
    palette.getManipulator().getDown().pressed = false;
    SwingUtilities.invokeLater(new Runnable() 
     {
       public void run() 
       {
         palette.getManipulator().getDown().repaint();
       }
     } );
  }
}

public void exportData(File path)
{   
    int buffer[][] = new int[32][32];
    HashSet<Integer> usedIndices = new HashSet<Integer>(); // Set of the color indices used in the picture
    
    // Join the color indices used in the picture in a flat data structure
    int[] t = new int[1024];
    int z = 0;
    for (int x = 0; x < 32; x++)
    {
      for (int y = 0; y < 32; y++)
      {
        t[z] = frameBuffer[x][y];
        buffer[x][y] = frameBuffer[x][y]; // Copy frameBuffer for later
        usedIndices.add(frameBuffer[x][y]); 
        z++;
      }
    }
    println("Anzahl unterschiedlicher Indices im Bild ist "+usedIndices.size());
    
    
    
    // 1. Write whole palette and picture = matrix of integers straight into a file - facilitates reloading picture and palette
    // Add the full palette
    String colorsString = new String();
    //colorsString += "\r\n";
    colorsString += "Complete Indexed Color Palette:" + "\r\n";
    for (int index = 0; index < 4096+768+64-1; index++)
    {
      int col = indexColor[index];
      //if (index == ledOffIndex) { col = color(0, 0, 0); } 
      colorsString += "444 Color " + index + ": ("+ round(red(col))+", " + round(green(col)) + ", " + (round(blue(col))) + "), ";
      colorsString += "333 Color (" +  (int) map((red(col)), 0, 15, 0, 7) + ", " + (int) map((green(col)), 0, 15, 0, 7) + ", " + (int) map((blue(col)), 0, 15, 0, 7) + ")";
      colorsString += "\r\n";
    }
    
    // Writes the unmodified indices that form the image into a String object
    String imgData = new String();
    imgData += "/*" + "\r\n" + colorsString;
    imgData += "*/\r\n/*\r\n";
    for (int y = 0; y < 32; y++)
    {
      for (int x = 0; x < 32; x++)
      {
        imgData = imgData + frameBuffer[x][y] + ", ";
      }
      imgData = imgData + "\r\n";
    }
    imgData += "*/";
    
    // Write a file into the current work directory
    // Not any more: for simplicity, the file name is standardized:
    //PrintWriter output = createWriter("ledmeplay_img_Reimport_" + day() + "_" + second() + "_" + minute() + "_" + hour() + ".txt"); 
    PrintWriter output = createWriter(path + "_reimport"); 
    output.println(imgData);
    output.flush();
    output.close();
    
    
    
    // 2. Make the indices in the picture as low as possible, then write the matrix of integers and only the indiced used in the picture into a file -
    // facilitates embedding the picture into Arduino code
    // This file's content cannot be read-back into LEDmePlay Draw
    
    // Associate old with new color indices
    Hashtable<Integer, Integer> oldAndNewIndex = new Hashtable<Integer, Integer>();
    int newIndex = 0;
    for(int oldIndex: usedIndices)
    {
      oldAndNewIndex.put(oldIndex, newIndex);
      newIndex++;
    }
    
    // Collect the colors, represented by their indices, in the picture
    // Write them out
    // Construct Arduino code blocks with the new color indices and the RGB values
    colorsString = new String();
    colorsString += "// Code that displays an image based on an indexed color palette" + "\r\n";
    colorsString += "void showImage()" + "\r\n";
    colorsString += "{" + "\r\n";
    colorsString += "  int col;" + "\r\n";
    colorsString += "  byte p;" + "\r\n";
    colorsString += "  byte img[32 * 32];" + "\r\n";
    colorsString += "  matrix.fillScreen(0, 0, 0);" + "\r\n";
    colorsString += "  for (int k = 0; k < 32 * 32; k++)" + "\r\n";
    colorsString += "  {" + "\r\n";
    colorsString += "    img[k] = pgm_read_byte_near(img + k);" + "\r\n";
    colorsString += "  }" + "\r\n";
    colorsString += "\r\n\r\n";
    colorsString += "  for (byte x = 0; x < 32; x++)" + "\r\n";
    colorsString += "  {" + "\r\n";
    colorsString += "    for (byte y = 0; y < 32; y++)" +"\r\n";
    colorsString += "    {" + "\r\n";
    colorsString += "      p = img[y * 32 + x];" + "\r\n";
    colorsString += "\r\n";
    colorsString += "      switch(p)" + "\r\n";
    colorsString += "      {" +"\r\n";
      
    for(int index: usedIndices)
    {
      int col; //= indexColor[index];
      int newInd = oldAndNewIndex.get(index);
      if (index == ledOffIndex) { col = color(0, 0, 0); } else { col = indexColor[index]; }
      
      colorsString += "      case " + newInd + ":" + "\r\n";
      colorsString += "      col = matrix.Color444(" + round(red(col))+ ", " + round(green(col)) + ", " + round(blue(col)) + ");" + "\r\n";
      colorsString += "      break;" + "\r\n\r\n";
    }
    
    colorsString += "      }" + "\r\n";
    colorsString += "    matrix.drawPixel(x, y, col);" +"\r\n";
    colorsString += "  }" + "\r\n";
    colorsString += "}" + "\r\n";
    

    // Exchange the color indices in the picture with the new indices
    // Write the matrix of color indices out
    colorsString += "\r\n\r\n";
    colorsString += "// Image data" + "\r\n";
    colorsString += "const uint8_t titleImg[] PROGMEM =" + "\r\n";
    colorsString += "{" +"\r\n";
    
    for (int x = 0; x < 32; x++)
    {
      for (int y = 0; y < 32; y++)
      {
        // Reassign the new color index to the entry
        buffer[x][y] = oldAndNewIndex.get(frameBuffer[x][y]);
      }
    }
    
    // Writes the data out to a new file and into a String object
    imgData = new String();
    imgData += colorsString;
    for (int y = 0; y < 32; y++)
    {
      imgData += "  ";
      for (int x = 0; x < 32; x++)
      {
        imgData = imgData + buffer[x][y] + ", ";
      }
      imgData = imgData + "\r\n";
    }
    
    imgData += "};" + "\r\n";
    
    // Writes the data into the system clipboard
    StringSelection data = new StringSelection(imgData);
    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
    clipboard.setContents(data, data);
    
    // Write a file to the Desktop
    // Not any more: for simplicity, the file name is standardized:
    //output = createWriter("ledmeplay_img_Arduino_" + day() + "_" + second() + "_" + minute() + "_" + hour() + ".txt"); 
    output = createWriter(path + "_Arduino"); 
    output.println(imgData);
    output.flush();
    output.close();
    
    
    /* 
      The following is not important here, as Processing uses 8/8/8 bit depth for RGB colors.
      Nevertheless, it is here for clarification and refers to the 16x32 and 32x32 RGB LED matrix panel...
      
      From: RGBmatrixPanel.cpp:
      ...
      Adafruit_GFX uses 16-bit color in 5/6/5 format, while matrix needs 4/4/4
      Pluck out relevant bits while separating into R,G,B:
      r =  c >> 12;        // RRRRrggggggbbbbb
      g = (c >>  7) & 0xF; // rrrrrGGGGggbbbbb
      b = (c >>  1) & 0xF; // rrrrrggggggBBBBb
      
      It can also convert 8/8/8 RGB values in HSV with gamma correction
      */
      
      diskOperationPending = false;
}


public void outputFileSelected(File selection)
{
  if (selection == null) 
  {
    println("File requester was closed, or user hit cancel.");
    return;
  }
  else
  {
    // Try to save picture and palettes to the specified output file
    exportData(selection);
  }
}

public void inputFileSelected(File selection) 
{
  int[][] tempBuffer = new int[32][32];
  
  if (selection == null) 
  {
    println("File requester was either closed, or the user hit cancel.");
  } 
  else 
  {
    // Try to load the palette in the selected file, if any
    println("Try to load picture and palette from file " + selection.getAbsolutePath());
      
    String lines[] = loadStrings(selection);
    println("There are " + lines.length + " lines in the file");
    //for (int i = 0 ; i < lines.length; i++) { println(lines[i]); }
    
    // We are looking for the section:
    /*
    // Complete Indexed Color Palette:
    444 Color 0: (3, 3, 3), 333 Color (1, 1, 1)
    444 Color 1: (14, 14, 14), 333 Color (6, 6, 6)
    ...
    444 Color 63: (8, 7, 15), 333 Color (3, 3, 7)
    */
    
    String[] m = new String[lines.length];
    //String[] n = new String[1];
    String sectionTitle = "^Complete Indexed";
    int row = 0;
    do
    {
      m = match(lines[row], sectionTitle);
      row++;
    }
    while ((m == null) && (row < lines.length));
    if (m != null) // There might be another section. We are looking for one with 4xxx color indices
    { 
      println("Headline: "+m[0]); 
      
      // Row is the first column with a Color444
      int colIndex = 0;
      String colorField = "Color \\d+: \\(\\d+, \\d+, \\d+\\)"; // Search for a color string in the current line
      do
      {
        m = match(lines[row], colorField);
        
        if (m != null) // Row contains a description of a Color444
        {
          // Get color index. 
          // Needed in case the colors were mixed up manually, i. e., are not in ascending order
          Pattern p = Pattern.compile("Color \\d+");
          Matcher ma = p.matcher(lines[row]);
          
          if (ma.find()) 
          { 
            String[] tokens = splitTokens(ma.group());
            colIndex = Integer.parseInt(tokens[1]);
            //println("Color index: "+colIndex);
          }
          
          // Get RGB values
          p = Pattern.compile("\\d+, \\d+, \\d+");
          ma = p.matcher(lines[row]);
          if (ma.find()) 
          { 
            String[] tokens = splitTokens(ma.group(), ", ");
            // Parse the red, green and blue color components from the line of text
            float redComponent = Integer.parseInt(tokens[0]);
            float greenComponent = Integer.parseInt(tokens[1]);
            float blueComponent = Integer.parseInt(tokens[2]);
            // Replace the color in the color selector with the new color
            if (colIndex != ledOffIndex) { indexColor[colIndex] = color(redComponent, greenComponent, blueComponent); } // Actualize the colors except for the "LED Off" color
          }
          //println("Row "+row);
          row++;
        } // Line contains a description of a Color444
      }
      while ((row < lines.length) && (colIndex < 4926));
      
      // Find the position where the picture data starts. It looks like this:
      println("Search for the matrix of color indices");
      /*
      1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
      ...
      1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
      */
      
      String sectionStartDivider = "\\/\\*";
      String sectionEndDivider = "\\*\\/";
      do
      {
        m = match(lines[row], sectionStartDivider);
        row++;
      }
      while ((m == null) && (row < lines.length));
      if (m != null) 
      { 
        println("Found the '/*'");
      }
      else
      {
        println("The file does not contain a matrix with color indices");
      }
      
      int ypos = 0; // We are in the first line of LED color indices data
      int xpos = 0; // We are in the first column of LED color indices data
      // Read in the next line of color indices
      do
      {
        xpos = 0;
        
        String[] tokens = splitTokens(lines[row], ", ");
        do
        {
          String s = tokens[xpos];
          //println("Reihe "+ypos+": "+lines[row]);
          if (match(s, "\\d+") != null) // s contains a decimal value
          {
            int index = Integer.parseInt(s);
            tempBuffer[xpos][ypos] = index;
          }
          else
          {
            println("There was an error in the color index matrix representation - abort");
            return;
          }
          if (xpos < 32) { xpos++; }
        }
        while (xpos < 32);
        
        ypos++; // Advance to the next line.
        row++;
      }
      while ((match(lines[row], sectionEndDivider) == null) && (row < lines.length)); // End of section not yet reached or EOF
    
      // At this point, we found and copied a section with 4928 color indices
      // We also found a section with picture data that we keep in a temporary buffer
      // Copy the picture data into the frame buffer for the currently displayed picture
      for (int x = 0; x < 32; x++)
      {
        for (int y = 0; y < 32; y++)
        {
          frameBuffer[x][y] = tempBuffer[x][y];
          backBuffer[x][y] = frameBuffer[x][y];
        }
      }    
      println("Copied picture data into frame buffer");
      
      fileLoaded = true;
      
      // Reset the undo history, make the new image the initial state
      undoHistory.reset();
    } // Another section was found 
    else
    {
      println("The file format is not suitable for LEDmePlay Draw.");
    }
  } // File selection is not empty
  
  diskOperationPending = false;
}

// ----------------------------------------------------------------------------------

public class PointTool implements DrawingTool
{
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean selected;
  //public boolean hasFocus;
  private ToolGroup group;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { repaint(); return true; }
    //hasFocus = false;
    return false;
  }
 
  public PointTool(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    //hasFocus = false;
    selected = false;
    repaint();
  }
  
  public void repaint()
  {
    strokeWeight(2);
    if (!selected)
    {
      fill(7);
      stroke(contourCol);
      rect(x, y, 50, 50);
      ellipse(x+25, y+25, 3, 3);
    }
    else
    {
      fill(contourCol);
      stroke(7);
      rect(x, y, 50, 50);
      ellipse(x+25, y+25, 3, 3);
    }
  }
  
  public void select() { this.selected = true; group.toggle(this); repaint(); }
  public void unselect() { this.selected = false; repaint(); }
  
  public void setToolGroup(ToolGroup group)
  {
    this.group = group;
    group.register(this);
  }
}

public class LineTool implements DrawingTool
{
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean selected;
  //public boolean hasFocus;
  private ToolGroup group;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { repaint(); return true; }
    //hasFocus = false;
    return false;
  }
 
  public LineTool(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    selected = false;
    repaint();
  }
  
  public void repaint()
  {
    if (!selected)
    {
      fill(7);
      stroke(contourCol);
      rect(x, y, w, h);
      line(x+10, y+10, x+40, y+40);
    }
    else
    {
      fill(contourCol);
      stroke(7);
      rect(x, y, w, h);
      line(x+10, y+10, x+40, y+40);
    }
  }
  
  public void select() { this.selected = true; group.toggle(this); repaint(); }
  public void unselect() { this.selected = false; repaint(); }
  
  public void setToolGroup(ToolGroup group)
  {
    this.group = group;
    group.register(this);
  }
}

public class BoxTool implements DrawingTool
{
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean selected;
  //public boolean hasFocus;
  private ToolGroup group;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    //if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { hasFocus = true; repaint(); return true; }
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { repaint(); return true; }
    //hasFocus = false;
    return false;
  }
 
  public BoxTool(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    //hasFocus = false;
    selected = false;
    repaint();
  }
  
  public void repaint()
  {
    if (!selected)
    {
      fill(7);
      stroke(contourCol);
      rect(x, y, 50, 50);
      stroke(contourCol);
      rect(x+10, y+10, 30, 30);
    }
    else
    {
      fill(contourCol);
      stroke(7);
       rect(x, y, 50, 50);
      rect(x+10, y+10, 30, 30);
    }
  }
  
  public void select() { this.selected = true; group.toggle(this); repaint(); }
  public void unselect() { this.selected = false; repaint(); }
  
  public void setToolGroup(ToolGroup group)
  {
    this.group = group;
    group.register(this);
  }
}

public class CircleTool implements DrawingTool
{
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean selected;
  //public boolean hasFocus;
  private ToolGroup group;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { repaint(); return true; }
    //hasFocus = false;
    //hasFocus = false;
    return false;
  }
 
  public CircleTool(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    //hasFocus = false;
    selected = false;
    repaint();
  }
  
  public void repaint()
  {
    if (!selected)
    {
      fill(7);
      rect(x, y, 50, 50);
      stroke(contourCol);
      ellipse(x+25, y+25, 30, 30);
    }
    else
    {
      fill(contourCol);
      //noStroke();
      stroke(7);
      rect(x, y, 50, 50);
      ellipse(x+25, y+25, 30, 30);
    }
  }
  
  public void select() { this.selected = true; group.toggle(this); repaint(); }
  public void unselect() { this.selected = false; repaint(); }
  
  public void setToolGroup(ToolGroup group)
  {
    this.group = group;
    group.register(this);
  }
}

public class FillTool implements DrawingTool
{
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean selected;
  //public boolean hasFocus;
  
  private ToolGroup group;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { repaint(); return true; }
    //hasFocus = false;
    return false;
  }
  
  public FillTool(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    //hasFocus = false;
    selected = false;
    repaint();
  }
  
  public void repaint()
  {
    if (!selected)
    {
      fill(7);
      rect(x, y, 50, 50);
      stroke(contourCol);
      rect(x+22, y+22, 15, 20);
      rect(x+24, y+15, 5, 7);
      line(x+7, y+9, x+16, y+14);
      line(x+7, y+18, x+16, y+18);
      line(x+7, y+27, x+16, y+22);
    }
    else
    {
      fill(contourCol);
      stroke(7);
      rect(x, y, 50, 50);
      rect(x+22, y+22, 15, 20);
      rect(x+24, y+15, 5, 7);
      line(x+7, y+9, x+16, y+14);
      line(x+7, y+18, x+16, y+18);
      line(x+7, y+27, x+16, y+22);
    }
  }
  
  public void select() { this.selected = true; group.toggle(this); repaint(); }
  public void unselect() { this.selected = false; repaint(); }
  
  public void setToolGroup(ToolGroup group)
  {
    this.group = group;
    group.register(this);
  }
}

class ToolGroup
{
  Vector<DrawingTool> group = new Vector<DrawingTool>();
  
  public void register(DrawingTool tool)
  {
    group.add(tool);
  }
  
  public Vector<DrawingTool> getTools()
  {
    return group;
  }
  
  public void toggle(DrawingTool tool) // Activates tool, inactivates the other tools 
  {
    for (DrawingTool t: group)
    {
      //tool.unselect();
      tool.repaint();
      if (t != tool) { t.unselect(); t.repaint(); }
    }
  }
}

interface DrawingTool
{
  int x = 0;
  int y = 0;
  int w = 0;
  int h = 0;
  public boolean selected = false;
  
  public void repaint();
  public void select();
  public void unselect();
  public boolean mouseOver(int x, int y);
  public void setToolGroup(ToolGroup t);
}

public class ExportButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean pressed;
  public boolean hasFocus;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { hasFocus = true; repaint(); return true; }
    return false;
  }
  
  public ExportButton(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    hasFocus = false;
    pressed = false;
    repaint();
  }
  
  public void repaint()
  {
    if (f == null) { f = loadFont("ArialMT-30.vlw"); }
    textFont(f, 30);
  
    if (pressed)
    {
      stroke(7);
      strokeWeight(2);
      fill(black);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(7);
      text("Export", x+10, y+35); 
      
      pressed = false;
    }
    else
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(contourCol);
      text("Export", x+10, y+35); 
    }
  }
}

public class ClearButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean pressed;
  public boolean hasFocus;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { hasFocus = true; repaint(); return true; }
    return false;
  }
  
  public ClearButton(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    hasFocus = false;
    pressed = false;
    repaint();
  }
  
  public void repaint()
  {
     if (f == null) { f = loadFont("ArialMT-30.vlw"); }
     textFont(f, 30);
     
    if (pressed)
    {
      stroke(7);
      strokeWeight(2);
      fill(black);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(7);
      text("Clear Panel", x+10, y+35); 
      
      pressed = false;
    }
    else
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(contourCol);
      text("Clear Panel", x+10, y+35); 
    }
  }
}

public class LoadButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean pressed;
  public boolean hasFocus;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { hasFocus = true; repaint(); return true; }
    return false;
  }
  
  public LoadButton(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    //hasFocus = false;
    pressed = false;
    repaint();
  }
  
  public void repaint()
  {
    textFont(f, 30);
    
    if (pressed)
    {
      stroke(7);
      strokeWeight(2);
      fill(black);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(7);
      text("Load", x+10, y+35); 
      
      pressed = false;
    }
    else
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(contourCol);
      text("Load", x+10, y+35); 
    }
  }
}

public class UndoButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean pressed;
  public boolean hasFocus;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { hasFocus = true; repaint(); return true; }
    return false;
  }
  
  public UndoButton(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    //hasFocus = false;
    pressed = false;
    repaint();
  }
  
  public void repaint()
  {
    textFont(f, 30);
    
    if (pressed)
    {
      stroke(7);
      strokeWeight(2);
      fill(black);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(7);
      text("Undo", x+10, y+35); 
      
      pressed = false;
    }
    else
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(contourCol);
      text("Undo", x+10, y+35); 
    }
  }
}

public class RedoButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  public boolean pressed;
  public boolean hasFocus;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { hasFocus = true; repaint(); return true; }
    return false;
  }
  
  public RedoButton(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    //hasFocus = false;
    pressed = false;
    repaint();
  }
  
  public void repaint()
  {
    textFont(f, 30);
    
    if (pressed)
    {
      stroke(7);
      strokeWeight(2);
      fill(black);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(7);
      text("Redo", x+10, y+35); 
      
      pressed = false;
    }
    else
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      rect(x, y, w, h);
      strokeWeight(1);
      fill(contourCol);
      text("Redo", x+10, y+35); 
    }
  }
}
// -----------------------------------------------


public class DownButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  private int type;
  private ColorManipulator cm;
  
  public boolean pressed;
  public boolean hasFocus;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { hasFocus = true; repaint(); return true; }
    return false;
  }
  
  public DownButton(ColorManipulator cm, int x, int y, int w, int h, int type)
  {
    this.cm = cm;
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.type = type;

    pressed = false;
    repaint();
  }
  
  public void push()
  {
    if (!cm.deactivated)
    {
      switch (type)
      {
        case RED: cm.setRedComponent(-1); break;
        case GREEN: cm.setGreenComponent(-1); break;
        case BLUE: cm.setBlueComponent(-1); break;
      }
      
      pressed = true;
      repaint();
      pressed = false;
    }
  }
  
  public void repaint()
  {
    if (f == null) { f = loadFont("ArialMT-30.vlw"); }
    textFont(f, 30);
  
    if (pressed)
    {
      stroke(7);
      strokeWeight(2);
      fill(black);
      triangle(x, y, x+16, y, x+8, y+16);
      strokeWeight(1);
      fill(7);
    }
    else if (!cm.deactivated)
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      triangle(x, y, x+16, y, x+8, y+16);
      fill(contourCol);
    }
    else
    {
      noStroke();
      rect(x-3, y-3, 22, 22);
      fill(6); 
    }
  }
}


public class UpButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  private int type;
  private ColorManipulator cm;
  
  public boolean pressed;
  public boolean hasFocus;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { hasFocus = true; repaint(); return true; }
    return false;
  }
  
  public UpButton(ColorManipulator cm, int x, int y, int w, int h, int type)
  {
    this.cm = cm;
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.type = type;

    pressed = false;
    repaint();
  }
  
  public void push()
  {
    if (!cm.deactivated)
    {
      switch (type)
      {
        case RED: cm.setRedComponent(1); break;
        case GREEN: cm.setGreenComponent(1); break;
        case BLUE: cm.setBlueComponent(1); break;
      }
      pressed = true;
      repaint();
    }
  }
  
  
  public void repaint()
  {
    if (f == null) { f = loadFont("ArialMT-30.vlw"); }
    textFont(f, 30);
  
    if (pressed)
    {
      stroke(7);
      strokeWeight(2);
      fill(black);
      triangle(x+8, y, x+16, y+16, x, y+16);
      strokeWeight(1);
      fill(7);
      
      pressed = false;
    }
    else if (!cm.deactivated)
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      triangle(x+8, y, x+16, y+16, x, y+16);
      fill(contourCol);
    }
    else
    {
      noStroke();
      fill(6); 
      rect(x-3, y-3, 22, 22);
    }
  }
}


public class SimpleDownButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  private NumberManipulator cm;
  
  public boolean pressed;
  public boolean hasFocus;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { hasFocus = true; repaint(); return true; }
    return false;
  }
  
  public SimpleDownButton(NumberManipulator cm, int x, int y, int w, int h)
  {
    this.cm = cm;
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;

    pressed = false;
    repaint();
  }
  
  public void push()
  {
    if (!cm.deactivated)
    {
     cm.setValue(-1);
      
      pressed = true;
      repaint();
      pressed = false;
    }
  }
  
  public void repaint()
  {
    if (f == null) { f = loadFont("ArialMT-30.vlw"); }
    textFont(f, 30);
  
    if (pressed)
    {
      stroke(7);
      strokeWeight(2);
      fill(black);
      triangle(x, y, x+16, y, x+8, y+16);
      strokeWeight(1);
      fill(7);
    }
    else if (!cm.deactivated)
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      triangle(x, y, x+16, y, x+8, y+16);
      fill(contourCol);
    }
    else
    {
      noStroke();
      rect(x-3, y-3, 22, 22);
      fill(6); 
    }
  }
}


public class SimpleUpButton
{
  private int x;
  private int y;
  private int w;
  private int h;
  private NumberManipulator cm;
  
  public boolean pressed;
  public boolean hasFocus;
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { hasFocus = true; repaint(); return true; }
    return false;
  }
  
  public SimpleUpButton(NumberManipulator cm, int x, int y, int w, int h)
  {
    this.cm = cm;
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;

    pressed = false;
    repaint();
  }
  
  public void push()
  {
    if (!cm.deactivated)
    {
      cm.setValue(1);
      pressed = true;
      repaint();
    }
  }
  
  
  public void repaint()
  {
    if (f == null) { f = loadFont("ArialMT-30.vlw"); }
    textFont(f, 30);
  
    if (pressed)
    {
      stroke(7);
      strokeWeight(2);
      fill(black);
      triangle(x+8, y, x+16, y+16, x, y+16);
      strokeWeight(1);
      fill(7);
      
      pressed = false;
    }
    else if (!cm.deactivated)
    {
      stroke(contourCol);
      fill (7);
      strokeWeight(2);
      triangle(x+8, y, x+16, y+16, x, y+16);
      fill(contourCol);
    }
    else
    {
      noStroke();
      fill(6); 
      rect(x-3, y-3, 22, 22);
    }
  }
}

public class ColorManipulator
{
  int x;
  int y;
  int w;
  int h;
  float red; // RGB 
  float green;
  float blue;
  int colIndex;
  String text;
  int[] indexCol;
  boolean deactivated = false;
  UpButton redUp;
  UpButton greenUp;
  UpButton blueUp;
  DownButton redDown;
  DownButton greenDown;
  DownButton blueDown;
  
    public ColorManipulator(int x, int y, int[] indexCol, int index, String text)
    {
      this.x = x;
      this.y = y;
      this.indexCol = indexCol;
      int col = indexCol[index];
      this.colIndex = index;
      this.red = red(col);
      this.green = green(col);
      this.blue = blue(col);
      this.text = text;
     
      redUp = new UpButton(this, x+180, y, 10, 10, RED);
      greenUp = new UpButton(this, x+230, y, 10, 10, GREEN);
      blueUp = new UpButton(this, x+280, y, 10, 10, BLUE);
      redDown = new DownButton(this, x+180, y+58, 10, 10, RED);
      greenDown = new DownButton(this, x+230, y+58, 10, 10, GREEN);
      blueDown = new DownButton(this, x+280, y+58, 10, 10, BLUE);
      
      repaint();
    }
    
    public void setRedComponent(int sign)
    {
      if ((sign == 1) && (round(red) < 15)) { red++; }
      else if ((sign == -1) && (round(red) > 0)) { red--; }
  
      indexCol[colIndex] = color(red, green, blue);
      repaint();
      restoreFrameBuffer();
      
      if      ((colIndex > 0) && (colIndex < 257)) { palette.repaintUpperLeftColorBox = true; palette.repaint(); }
      else if ((colIndex > 256) && (colIndex < 512)) { palette.repaintUpperRightColorBox = true; palette.repaint(); }
      else if ((colIndex > 511) && (colIndex < 769)) { palette.repaintLowerLeftColorBox = true; palette.repaint(); }
      else if ((colIndex > 768) && (colIndex < 4864)) { palette.repaintDynamicColorBox = true; palette.repaint(); }
      else if (colIndex > 4863) { palette.repaint64ColorBox = true; palette.repaint(); }
    }
  
    public void setGreenComponent(int sign)
    {
      if ((sign == 1) && (round(green) < 15)) { green++; }
      else if ((sign == -1) && (round(green) > 0)) { green--; }
      indexCol[colIndex] = color(red, green, blue);
      repaint();
      restoreFrameBuffer();
      
      if      ((colIndex > 0) && (colIndex < 257)) { palette.repaintUpperLeftColorBox = true; palette.repaint(); }
      else if ((colIndex > 256) && (colIndex < 512)) { palette.repaintUpperRightColorBox = true; palette.repaint(); }
      else if ((colIndex > 511) && (colIndex < 769)) { palette.repaintLowerLeftColorBox = true; palette.repaint(); }
      else if ((colIndex > 768) && (colIndex < 4864)) { palette.repaintDynamicColorBox = true; palette.repaint(); }
      else if (colIndex > 4863) { palette.repaint64ColorBox = true; palette.repaint(); }
    }
  
    public void setBlueComponent(int sign)
    {
      if ((sign == 1) && (round(blue) < 15)) { blue++; }
      else if ((sign == -1) && (round(blue) > 0)) { blue--; }
      indexCol[colIndex] = color(red, green, blue);
      repaint();
      restoreFrameBuffer();
      
      if      ((colIndex > 0) && (colIndex < 257)) { palette.repaintUpperLeftColorBox = true; palette.repaint(); }
      else if ((colIndex > 256) && (colIndex < 512)) { palette.repaintUpperRightColorBox = true; palette.repaint(); }
      else if ((colIndex > 511) && (colIndex < 769)) { palette.repaintLowerLeftColorBox = true; palette.repaint(); }
      else if ((colIndex > 768) && (colIndex < 4864)) { palette.repaintDynamicColorBox = true; palette.repaint(); }
      else if (colIndex > 4863) { palette.repaint64ColorBox = true; palette.repaint(); }
    }
    
    public void repaint()
    {
      if (f == null) { f = loadFont("ArialMT-30.vlw"); }
      textFont(f, 30);
      
      // Field for red component
      noFill();
      stroke(contourCol);
      strokeWeight(2);
      fill(7);
      rect(x+168, y+22, 45, 30);
      rect(x+218, y+22, 45, 30);
      rect(x+268, y+22, 45, 30);
      fill(black);
      noStroke();
      text((int)(round(red)), x+174, y+48);
      text((int)(round(green)), x+224, y+48);
      text((int)(round(blue)), x+274, y+48);
      fill(6);
      rect(x+168, y+77, 140, 35);
      rect(x, y+20, 95, 30);
      fill(black);
      text("R", x+178, y+105);
      text("G", x+228, y+105);
      text("B", x+278, y+105);
      
      // Draw color box
      fill(color(red, green, blue));
      stroke(contourCol);
      strokeWeight(2);
      rect(x+108, y+22, 40, 30);
      fill(black);
      noStroke();
      text(text, x+6, y+48);  
      
      // Draw buttons
      redUp.repaint();
      greenUp.repaint();
      blueUp.repaint();
      redDown.repaint();
      greenDown.repaint();
      blueDown.repaint();
    }
    
    public void setNewColorIndex(int index)
    {
      this.colIndex = index;
      int col = indexCol[index];
      this.red = red(col);
      this.green = green(col);
      this.blue = blue(col);
      repaint();
    }
    
    public UpButton getRedUp() { return this.redUp; }
    public UpButton getGreenUp() { return this.greenUp; }
    public UpButton getBlueUp() { return this.blueUp; }
    public DownButton getRedDown() { return this.redDown; }
    public DownButton getGreenDown() { return this.greenDown; }
    public DownButton getBlueDown() { return this.blueDown; }
}


public class NumberManipulator
{
  int x;
  int y;
  int w;
  int h;
  float red; // RGB 
  float green;
  float blue;
  int colIndex;
  String text;
  int value;
  int[] indexCol;
  boolean deactivated = false;
  SimpleUpButton up;
  SimpleDownButton down;
  
    public NumberManipulator(int x, int y, int[] indexCol, int value, String text)
    {
      this.x = x;
      this.y = y;
      this.indexCol = indexCol;
      this.value = value;
      this.text = text;
     
      up = new SimpleUpButton(this, x+180, y, 10, 10);
      down = new SimpleDownButton(this, x+180, y+58, 10, 10);
      
      repaint();
    }
    
    public void setValue(int sign)
    {
      if ((sign == 1) && (value < 15)) { value++; }
      else if ((sign == -1) && (value > 0)) { value--; }
  
      repaint();
      palette.switchPalette(value);
      //restoreFrameBuffer();
      //palette.repaint();
    }
    
    public void repaint()
    {
      if (f == null) { f = loadFont("ArialMT-30.vlw"); }
      textFont(f, 30);
      
      // Field for color component
      noFill();
      stroke(contourCol);
      strokeWeight(2);
      fill(7);
      rect(x+168, y+22, 45, 30);
      fill(black);
      text((int)(round(value)), x+174, y+48);
      
      text("B", x+179, y+105);
      
      // Draw buttons
      up.repaint();
      down.repaint();
    }
    
    public void setNewValue(int value)
    {
      this.value = value;
      repaint();
    }
    
    public SimpleUpButton getUp() { return this.up; }
    public SimpleDownButton getDown() { return this.down; }
}




// ---------------------------------------------------------------------------

class RandomColorPalette
{
  int[] indexColor; 
  int x;
  int y;
  int w;
  int h;
  boolean hasFocus;
  
  public RandomColorPalette(int x, int y, int w, int h, int[] indexColor)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.indexColor = indexColor;
    
    this.indexColor[1] = color(15, 15, 15);
    this.indexColor[0] = color(2, 2, 2);
    
    hasFocus = false;
    makeRandomRGB444Palette();
    repaint();
  }
  
  public void makeRandomRGB444Palette()
  {
    randomSeed(45);
    for (int i = 2; i < 64; i++)
    {
      this.indexColor[i] = color(random(0, 16), random(0,16), random(0, 16));
    }
  }
  
  public void repaint()
  {
    // Paint the index color palette on-screen
    if (f == null) { f = loadFont("ArialMT-30.vlw"); }
    textFont(f, 30);
    int x = this.x;
    int y = this.y;
    for (int index = 0; index < 64; index++)
    {
      noStroke();
      fill(0);
      textAlign(LEFT);
      text(""+index, x+3, 32+y);
      
      stroke(contourCol);
      int col = indexColor[index];
      fill(col);
      strokeWeight(2);
      rect(x, 40+y, 45, 45);
      
      if (x >= this.x+750) { x = this.x; y = y + 100; }
      else { x = x + 50; }
    } 
    
    // Highlight the color under the mouse cursor
    stroke(contourCol);
    strokeWeight(2);
    noFill();
    if (hasFocus)
    {
      clearHighlightedFrame();
        
      // Mark index color currently under the mouse cursor
      currentColor_x = (mouseX - (this.x+4)) / 50;
      if      ((mouseY > this.y+47) && (mouseY < this.y+94)) { currentColor_y = this.y+40; }
      else if ((mouseY > this.y+147) && (mouseY < this.y+194)) { currentColor_y = this.y+140; }
      else if ((mouseY > this.y+247) && (mouseY < this.y+294)) { currentColor_y = this.y+240; }
      else if ((mouseY > this.y+347) && (mouseY < this.y+394)) { currentColor_y = this.y+340; }
      
      stroke(red);
      if ((currentColor_y >= this.y+40) && (currentColor_y <= this.y+340)) 
      { 
        rect(this.x + currentColor_x * 50, currentColor_y, 45, 45); 
      }
        
      formerColor_x = currentColor_x;
      formerColor_y = currentColor_y;
        
      currentColorIndex = ((currentColor_y - 50)/100)*16 + currentColor_x;
    }
  }
  
  public void clearHighlightedFrame()
  {
    // Clear highlighted frame in color selector
    if ((formerColor_y >= this.y+40) && (formerColor_y <= this.y+340))
    {
      stroke(contourCol);
      noFill();
      rect(this.x + formerColor_x * 50, formerColor_y, 45, 45);
    }
  }
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { hasFocus = true; repaint(); return true; }
    hasFocus = false;
    return false;
  }
} // class RandomColorPalette




class ColorRGB444Palette
{
  int[] indexColor; 
  int x;
  int y;
  int w;
  int h;
  boolean hasFocus;
  //boolean hasLostFocus = false;
  
  //public boolean eraseMarker;
 
  int defaultBlue;
  int dynamicIndex; // First index in the dynamic palette in the lower right
  int offsetIndex;
  int boxX = this.x; // Upper left position of a single color box in one of the four quadrants
  int boxY = this.y;
  int oldBoxX = boxX;
  int oldBoxY = boxY;
  int col64X = this.x+720;
  int col64Y = this.y;
  int oldCol64X = -20;
  int oldCol64Y = -20;
    
  NumberManipulator blueManipulator;
 
  public boolean repaintDynamicColorBox;
  public boolean repaintUpperLeftColorBox;
  public boolean repaintUpperRightColorBox;
  public boolean repaintLowerLeftColorBox;
  public boolean repaint64ColorBox;
  
  public ColorRGB444Palette(int x, int y, int w, int h, int[] indexColor, int blueValue)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.indexColor = indexColor;
    this.defaultBlue = blueValue;
    this.dynamicIndex = switchPalette(defaultBlue);
    
    this.indexColor[ledOffIndex] = color(2, 2, 2); // "LED Off", immutable color
    
    hasFocus = false;
    makeColorRGB444Palette();
    blueManipulator = new NumberManipulator(x+479, y+340, indexColor, defaultBlue, "");
    
    paintUpperLeftColorBox();
    paintUpperRightColorBox();
    paintLowerLeftColorBox();
    paintDynamicColorBox();
    strokeWeight(2);
    stroke(contourCol);
    noFill();
    rect(this.x-10, this.y-10, 650, 650);
    
    paint64ColorBox();
    
    repaintUpperLeftColorBox = false;
    repaintUpperRightColorBox = false;
    repaintLowerLeftColorBox = false;
    repaintDynamicColorBox = true;
    repaint64ColorBox = true;
  }
  
  public void makeColorRGB444Palette()
  {
    int i = 0; // We start at color 1. Color 0 is "LED Off" and needs to be immutable
    int r;
    int g;
    int b;
    
    b = 0;
    for (r = 0; r < 16; r++)
    {
      for (g = 0; g < 16; g++)
      {
        this.indexColor[i] = color(r, g, b);
        i++;
      }
    }
    
    r = 0;
    for (g = 0; g < 16; g++)
    {
      for (b = 0; b < 16; b++)
      {
        this.indexColor[i] = color(r, g, b);
        i++;
      }
    }
    
    g = 0;
    for (r = 0; r < 16; r++)
    {
      for (b = 0; b < 16; b++)
      {
        this.indexColor[i] = color(r, g, b);
        i++;
      }
    }
    
    // Create the remaining colors with custom blue channel
    for (b = 0; b < 16; b++)
    {
      for (g = 0; g < 16; g++)
      {
        for (r = 0; r < 16; r++)
        {
        
            this.indexColor[i] = color(r, g, b);
            i++;
        }
      }
    }
    //println(i); // Number of last index in array indexColor
    
    // Make a random palette with 64 colors at the end of the color array
    for (int j = i+1; j < i+64; j++)
    {
      this.indexColor[j] = color(random(0, 16), random(0, 16), random(0,16));
    }
  }
  
  public int switchPalette(int page)
  {
    dynamicIndex = 768 + page*256;
    repaintDynamicColorBox = true;
    repaint();
    
    return dynamicIndex;
  }
  
  public void writeColorComponents()
  {
    fill(0);
    textFont(f,30);
    fill(6, 6, 6);
    noStroke();
    rect(39, 920, 900, 40);
    fill(black);
    int col = indexColor[currentColorIndex];
    String s = "";
    if (currentColorIndex != 4864) 
    { 
      s = "RGB444 Color " + currentColorIndex + ":  (" + round(red(col)) + ", "+ round(green(col)) + ", "+ round(blue(col)) + ")";
    }
    else
    {
      s = "LED Off Color - immutable";
    }
    
    // Write the current color indices for Color1 and Color2
    s = s + ",  Color1: "+indexColor1+", Color2: "+indexColor2;
    
    text(s, 40, 950);
    
  }
  
  public void paint64ColorBox()
  { 
    int x = this.x + 730;
    int y = this.y;
    
    // Make a border around the ledOff color
    fill(10);
    noStroke();
    rect(this.x+720, this.y-1, 41, 32);
    
    for (int index = ledOffIndex; index < ledOffIndex+64; index++)
    {
      stroke(contourCol);
      int col = indexColor[index];
      fill(col);
      strokeWeight(2);
      rect(x, y, 30, 30);
      
      if (x >= this.x+730+100) { x = this.x+730; y = y + 35; }
      else { x = x + 35; }
    } 
    
    noFill();
    strokeWeight(2);
    rect(this.x+720, this.y-9, 155, 574);
  }
  
  public void paintUpperLeftColorBox()
  {
    // Draw colors with indices 1..256  
    int x = this.x;
    int y = this.y;
    
    for (int index = 0; index < 256; index++)
    { 
      stroke(contourCol);
      int col = indexColor[index];
      fill(col);
      strokeWeight(2);
      rect(x, y, 15, 15);
      
      if (x >= this.x+280) { x = this.x; y = y + 19; }
      else { x = x + 19; }
    } 
  }
  
  public void paintUpperRightColorBox()
  {
    // Draw colors with indices 257..512 
    int x = this.x+330;
    int y = this.y;
    for (int index = 256; index < 512; index++)
    { 
      stroke(contourCol);
      int col = indexColor[index];
      fill(col);
      strokeWeight(2);
      rect(x, y, 15, 15);
      
      if (x >= this.x+610) { x = this.x+330; y = y + 19; }
      else { x = x + 19; }
    }
  }
  
  public void paintLowerLeftColorBox()
  {
    // Draw colors with indices 513..768
    int x = this.x;
    int y = this.y+330;
    for (int index = 512; index < 768; index++)
    { 
      stroke(contourCol);
      int col = indexColor[index];
      fill(col);
      strokeWeight(2);
      rect(x, y, 15, 15);
      
      if (x >= this.x+270) { x = this.x; y = y + 19; }
      else { x = x + 19; }
    } 
  }
  
  public void paintDynamicColorBox()
  {
    // Draw colors with custom blue channel, indices 769..1024
    int x = this.x+330;
    int y = this.y+330;
    for (int index = dynamicIndex; index < dynamicIndex+256; index++)
    { 
      stroke(contourCol);
      int col = indexColor[index];
      fill(col);
      strokeWeight(2);
      rect(x, y, 15, 15);
      
      if (x >= this.x+610) { x = this.x+330; y = y + 19; }
      else { x = x + 19; }
    } 
  }
  
  public void repaint()
  {
    highlightColorBox();
  
    if (repaintUpperLeftColorBox) { paintUpperLeftColorBox(); repaintUpperLeftColorBox = false; }
    if (repaintUpperRightColorBox) { paintUpperRightColorBox(); repaintUpperRightColorBox = false; }
    if (repaintLowerLeftColorBox) { paintLowerLeftColorBox(); repaintLowerLeftColorBox = false; }
    if (repaintDynamicColorBox) { paintDynamicColorBox(); repaintDynamicColorBox = false; }
    if (repaint64ColorBox) { paint64ColorBox(); repaint64ColorBox = false; }
  }
  
  public void highlightColorBox()
  { 
      // Erase the highlighted box at the old position of the mouse cursor
      if ((oldBoxX != boxX) || (oldBoxY != boxY))
      {
        stroke(contourCol);
        noFill();
        rect(oldBoxX, oldBoxY, 15, 15);
      }
      
      // Highlight the color under the mouse cursor
      stroke(red);
      strokeWeight(2);
      oldBoxX = boxX;
      oldBoxY = boxY;
      
      // Erase the highlighted box at the old position of the mouse cursor in the 64 color-palette
      stroke(contourCol);
      noFill();
      rect(this.x - 5 + oldCol64X*35, this.y + oldCol64Y*35, 30, 30);
      
      if ((mouseX > this.x) && (mouseX < this.x+299) && (mouseY > this.y) && (mouseY < this.y+299))
      {
        // Mouse is in upper left quadrant 
        offsetIndex = 0;

        oldBoxX = this.x + boxX * 19;
        oldBoxY = this.y + boxY * 19;
       
        // Mark index color currently under the mouse cursor
        noFill();
        stroke(red);
        boxX = (mouseX - this.x) / 19;
        boxY = (mouseY - this.y) / 19;
        rect(this.x + boxX * 19, this.y+boxY*19, 15, 15);
        
        currentColorIndex = (boxY * 16) + boxX;
        writeColorComponents();
      }
      else if ((mouseX > this.x+330) && (mouseX < this.x+330+299) && (mouseY > this.y) && (mouseY < this.y+299))
      {
        // Mouse is in upper right quadrant 
        offsetIndex = 256;
        
        oldBoxX = this.x + boxX * 19 + 330;
        oldBoxY = this.y + boxY * 19;
        
        // Mark index color currently under the mouse cursor
        stroke(red);
        noFill();
        boxX = (mouseX-330 - this.x) / 19;
        boxY = (mouseY - this.y) / 19;
        rect(this.x + boxX * 19 + 330, this.y+boxY*19, 15, 15); 
        
        currentColorIndex = 256 + (boxY * 16) + boxX;
        writeColorComponents();
      }
      else if ((mouseX > this.x) && (mouseX < this.x+299) && (mouseY > this.y+330) && (mouseY < this.y+330+299))
      {
        // Mouse is in lower left quadrant 
        offsetIndex = 512;
        
        oldBoxX = this.x + boxX * 19;
        oldBoxY = this.y+boxY * 19 + 330;
        
        // Mark index color currently under the mouse cursor
        noFill();
        stroke(red);
        boxX = (mouseX - this.x) / 19;
        boxY = (mouseY-330 - this.y) / 19;
        rect(this.x + boxX * 19, this.y+boxY*19 + 330, 15, 15); 
        
        currentColorIndex = 512 + (boxY * 16) + boxX;
        writeColorComponents();
      }
      else if ((mouseX > this.x+330) && (mouseX < this.x+330+299) && (mouseY > this.y+330) && (mouseY < this.y+330+299))
      {
        // Mouse is in the dynamic (lower right) quadrant 
        // dynamicIndex is the index of the first color in the dynamic quadrant

        oldBoxX = this.x + boxX * 19 + 330;
        oldBoxY = this.y + boxY * 19 + 330;
        
        // Mark index color currently under the mouse cursor
        stroke(red);
         noFill();
        boxX = (mouseX-330 - this.x) / 19;
        boxY = (mouseY-330 - this.y) / 19;
        rect(this.x + boxX * 19 + 330, this.y+boxY*19 + 330, 15, 15); 
        
        currentColorIndex = dynamicIndex + (boxY * 16) + boxX;
        writeColorComponents();
      }
      else if ((mouseX > this.x+715) && (mouseX < this.x+900) && (mouseY > this.y-9) && (mouseY < this.y+560)) // 64 Color Palette
      {
        if ((mouseX > this.x + 730) && (mouseX < this.x + 870))
        {
          oldCol64X = col64X;
          oldCol64Y = col64Y;
          
          // Highlight color box under the mouse cursor
          stroke(red);
          noFill();
          col64X = (mouseX + 5 - this.x) / 35;
          col64Y = (mouseY - this.y) / 35; 
          rect(this.x - 5 + col64X*35, this.y + col64Y*35, 30, 30);
          
          currentColorIndex = 4864 + (col64Y-1)*4 + col64X - 17;
          writeColorComponents();
        }
      }
      
  }
  
  public boolean mouseOver(int mx, int my)
  {
    // (mx, my) = mouse cursor position
    if ((mx >= x) && (mx <= x+w) && (my >= y) && (my <= y+h)) { hasFocus = true; repaint(); return true; }
    hasFocus = false;
    return false;
  }
  
  public NumberManipulator getManipulator()
  {
    return this.blueManipulator;
  }
  
  public int getDynamicIndex() { return this.dynamicIndex; }
  
} // class Color444Palette


// Undo/Redo buffer with a capacity of numSteps
// Could be implemented more elegantly using a Stack
// Editing an intermediate state clears all subsequent states
class UndoBuffer
{
  int[][][] buffer;
  int currentState;
  int maxNumStates;
  int statesUsed;
  
  public UndoBuffer(int numStates)
  {
    this.buffer = new int[numStates][32][32];
    this.maxNumStates = numStates;
    this.currentState = 0;
    this.statesUsed = 0;
    
    prepareBuffer(currentState);
  }
  
  public void prepareBuffer(int state)
  {
    for (int x = 0; x < 32; x++)
    {
      for (int y = 0; y < 32; y++)
      {
        buffer[state][x][y] = ledOffIndex;
      }
    }
  }
  
  
  // Adds snapshot of LED matrix panel content to buffer
  public void addState()
  {
    if (currentState == maxNumStates) 
    {
      println("Undo buffer full - no more undo steps possible");
    }
    else
    {
      currentState++;
      println("Added state "+currentState);
      statesUsed++;
      
      for (int x = 0; x < 32; x++)
      {
        for (int y = 0; y < 32; y++)
        {
          buffer[currentState][x][y] = frameBuffer[x][y];
        }
      }  
    }
    
  }
  
  // Replaces current state and ignore subsequent states
  public void replaceAsLast()
  {
    println("Make the current, modified state the newest state" + currentState);
    
     if (currentState < maxNumStates) 
     {
        // Abuse next state: replace it by current frame buffer   
        for (int x = 0; x < 32; x++)
        {
          for (int y = 0; y < 32; y++)
          {
            buffer[currentState+1][x][y] = frameBuffer[x][y];
            backBuffer[x][y] = buffer[currentState+1][x][y]; // Mirror the image also into the back buffer
          }
        }
     }
    
    /*
    for (int state = currentState+2; state < maxNumStates; state++)
    {
      buffer[currentState] = new int[32][32];
    }
    */
    
    currentState++;
    statesUsed = currentState;
  }
  
  // Makes snapshot of LED matrix panel the initial state of the undo buffer
  public void reset()
  {
    println("Emptied the undo buffer");
    statesUsed = 0;
    currentState = 0;
      
    for (int x = 0; x < 32; x++)
    {
      for (int y = 0; y < 32; y++)
      {
        buffer[currentState][x][y] = frameBuffer[x][y];
      }
    }
  }
  
  
  public void goBack()
  {
    if (currentState > 0)
    {
      currentState--;
      
      println("Go back to state "+currentState);
      
      for (int x = 0; x < 32; x++)
      {
        for (int y = 0; y < 32; y++)
        {
          frameBuffer[x][y] = buffer[currentState][x][y];
          backBuffer[x][y] = buffer[currentState][x][y]; // Mirror the image also into the back buffer
        }
      }
      
      drawEmptyMatrix();
      drawFrameBuffer();
    }
    else
    {
      println("No previous undo steps recorded");
    }
  }
  
  public void goForward()
  {
    if ((currentState < maxNumStates) && (currentState < statesUsed))
    {
      currentState++;
      
       println("Go forward to state "+currentState);
      
      for (int x = 0; x < 32; x++)
      {
        for (int y = 0; y < 32; y++)
        {
          frameBuffer[x][y] = buffer[currentState][x][y];
          backBuffer[x][y] = buffer[currentState][x][y]; // Mirror the image also into the back buffer
        }
      }
      
      drawEmptyMatrix();
      drawFrameBuffer();
    }
    else
    {
      println("Arrived at latest undo step");
    }
  }
}
  public void settings() {  size(1860, 970);  noSmooth(); }
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "LEDmePlayDraw_270117" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
