#include "Convac.h"
#include <stdlib.h> // exit
/*===============*
* Rezept-Editor *
*===============*/
/* Im Gegensatz zu dlgAnalog und dlgDigital ist dlgRezept sehr umfangreich.
Die Rezeptliste wird als Baumdiagramm mit schließbaren Knoten angezeigt.
Die linke Spalte ist reserviert für die Programmlauf-Anzeige (Pfeil),
der das gerade aktive Rezept (in Ruhe) bzw. den gerade aktiven Schritt
(in Aktion) anzeigt. Eventuell auch mal Haltepunkte.
Der übrige Bereich dient als Listenanzeige (mit Rollbalken) für den Baum.
Alle Elemente können in-place editiert, hinzugefügt oder gelöscht werden,
auch während des Programmablaufs.
Während es für Rezepte nur die Eingabe der ID
und für Schritte nur die Eingabe der Zeit gibt,
gibt es für Befehle /vier/ Eingabeelemente:
- Bedingung oder (bedingungslose) Ausgabe-Aktion
- Aktion bei Nichterfüllung der Bedingung
- Betreffender Analog- oder Digitalkanal
- Wert (zu prüfen oder auszugeben)
Im Programmlauf werden Digitalwerte sofort
und Analogwerte in einer linearen Rampe über die Dauer des Schritts ausgegeben.
Geprüft werden Analog- und Digitalwerte permanent während der Schritt-Zeit.
Rezepte können keine Schleifen enthalten, es sind allesamt Sequenzen.
*/
// Endadresse im „dynamischen“ Speicher
static byte*gsend() {return ((byte*)&eedata.gs)+gslen;}
// Die beiden „fundamentalen“ Werte des dynamischen Speichers berechnen:
// rl = Anfang der Rezeptliste
// gslen = Länge der dynamischen Daten
word GS::calcsizes() {
rl=eedata.gs.fin();
return gslen=rl->size()+(byte*)rl-(byte*)&eedata.gs;
}
// „Dynamischen“ Speicher bewegen: Platz machen oder Platz sparen.
// Neuer Speicher wird mit Null gelöscht.
// Liefert false wenn kein Platz
bool GS::move(byte*dst,byte*src) {
#ifdef DEBUG
if (dst<=(byte*)&eedata.gs) exit(-21);
if (dst>(byte*)&eedata+sizeof eedata) exit(-22);
if (src<=(byte*)&eedata.gs) exit(-21);
if (src>(byte*)&eedata+sizeof eedata) exit(-22);
#endif
int diff=dst-src;
if (!diff) return true; // Nichts tun: Quelle == Ziel
if (diff>int(sizeof eedata-sizeof(CONFIG)-gslen)) return false;
word l=gsend()-src; // Länge der zu schaufelnden Daten in Bytes
memmove(dst,src,l); // (l kann Null sein)
if (diff>=0) memset(src,0,diff);
return true;
}
// Speicherbereich in-place links rotieren (nicht zwingend im Dynamischen Speicher)
// eadr = letzte Speicherzelle, NICHT darüberhinaus
// len = Anzahl der byteweisen Linksschiebeoperationrn
// Kode-Kürze geht hier vor Performance!!
static void memrol(byte*aadr,byte*eadr,word cnt) {
#if 1
asm(
"0: movw r30,%A0 \n"
" ld r1,Z \n"
"1: ldd r0,Z+1 \n"
" st Z+,r0 \n"
" cp r30,%A1 \n"
" cpc r31,%B1 \n"
" brne 1b \n"
" st Z,r1 \n"
" subi %A2,1 \n"
" sbci %B2,0 \n"
" brne 0b \n"
" clr r1 \n"
::"r"(aadr),"r"(eadr),"r"(cnt):"r30","r31");
#else
do{
byte save=*aadr;
memcpy(aadr,aadr+1,eadr-aadr);
*eadr=save;
}while(--cnt);
#endif
}
/* Der Rezept-Editor verwendet ebenfalls die ListView wie der Analog-Editor,
jedoch als Baumdiagramm mit auf- und zuklappbaren Knoten.
*/
static void plusminus(byte x, N n) {
d.X=x; d.print(n.f?0x1B:0x1A); // Eingerahmtes Minus oder Plus
}
// generiert Blinken und führt das Fokus-Sichtbarkeitsbit mit
static void xorFocus() {
lvXorFocus(C.focusr);
}
// Fokusrechteck weg (vor dem Neuzeichnen von _Teilen_ des invaliden Bereichs)
inline void hideFocus() {if (focusVis) xorFocus();}
// Fokusrechteck anzeigen (nach dem Neuzeichnen des invaliden Bereichs)
inline void showFocus() {if (!focusVis) xorFocus();}
// Aufruf durch Ändern des aktuellen Rezepts
// flags.0: Rezept oder Schritt markieren
// flags.7: Von paint(), Clipping-Bereich nicht aufreißen
static void xorMarker(byte flags=0) {
ITER iter; // Lokales <iter> (6 Bytes)
iter.ire=flags&3?run.rezept:C.runrez;
iter.ist=run.aktion;
iter.type=flags&3;
byte line=iter.line(); // Zeile ermitteln
if (line==0xFF) return; // kann nur bei flags&3!=0 passieren
if (!(flags&0x80)) lv.setFullClip();
flags&=3;
d.flags=d.COLOR|d.R2_XORPEN;
int y=lvScreenY(line);
if (y<-10) return; // außerhalb oben (kann aber partiell gemalt werden!)
if (y>64) return; // außerhalb unten
d.gotoXY(1+(flags<<2),y);
d.print(0x1E);
}
// Arbeitet mit lokaler <iter>-Struktur beim Durchlaufen
static void treepaint() {
markNo(); // Bild ungültig, idle() darf nicht hineinmalen
byte line=0;
N nSt;
d.flags=d.COLOR|d.R2_COPYPEN;
ITER iter; // Lokales <iter> (6 Bytes)
if (showSafe) {iter.initgs(); iter.ire=0xFF; nSt.b=1; goto innen;}
aussen:
iter.init(); // dreistufiger Baum
for (iter.ire=0; iter.ire<rl->nRezept;) {
nSt=iter.re->n;
if (lv.itemInvalid(line)) {
d.Y=lvScreenY(line);
if (nSt.c>!showSafe) plusminus(6,nSt);
d.X=16; // 1 Pixel Luft
iter.re->print(iter.ire);
}
line++; idle();
iter.st=iter.re->st0(); // jetzt Zeiger auf SCHRIT
innen:
for (iter.ist=0; iter.ist<nSt.c; iter.ist++) {
N nBe;
nBe.b=iter.st->n.b|nSt.b&0x80;
// für den Compiler umständlicher: nBe=iter.a->n; nBe.f|=nSt.f;
if (showSafe||iter.ist) { // Schritt-Zeile darstellen
if (!nSt.f) { // (wenn Rezept aufgeklappt)
if (lv.itemInvalid(line)) {
d.Y=lvScreenY(line);
if (nBe.c) plusminus(14,nBe);
d.X=24; // 1 Pixel Luft
iter.st->print(iter.ist);
}
line++; idle();
}
}else{ // Schritt-Zeile nicht darstellen
nBe.b|=0x80; // Befehlsausgabe (weiter unten) unterdrücken
}
iter.be=iter.st->be0(); // jetzt Zeiger auf BEFEHL
for (iter.ibe=0; iter.ibe<nBe.c; iter.ibe++) {
if (!(nBe.f)) {
if (lv.itemInvalid(line)) {
d.gotoXY(32,lvScreenY(line));
iter.be->print(iter.ibe);
}
line++; idle();
}
iter.be=iter.be->fin();
}/*nBe*/
}/*nSt*/
if (!++iter.ire) goto aussen; // weiter mit Rezeptliste (nRezept überspringen)
}/*nRezept*/
markYes(); // Bild ist gültig, idle() darf hineinmalen
xorMarker(0x80);
if (run.aktion) xorMarker(0x81);
xorFocus(); // immer vollständig sichtbar
}
// Hinterlässt iter mit dem richtigen Zeiger für C.focusr
// und den Indexeinträgen ire, ist und ibe (soweit zutreffend).
// Setzt den Typ des Zeigers, 0 = REZEPT*, 1=SCHRIT*, 2=BEFEHL*
// In Globaler Sicherheit ist .ire==0xFF und .ist==n; .type = 1 oder 2
void ITER::whereami() {
byte cnt=C.focusr+1;
N nSchrit;
if (showSafe) {initgs(); ire=0xFF; nSchrit.b=1; goto innen;}
aussen:
init();
for (ire=0;ire<rl->nRezept;) {
type=0;
if (!--cnt) return; // Rezepte sind immer sichtbar
nSchrit=re->n;
st=re->st0();
innen:
for (ist=0;ist<nSchrit.c;ist++) {
type=1;
if (!nSchrit.f && (showSafe||ist) && !--cnt) return; // nur zählen wenn aufgeklappt
N nBefehl;
nBefehl.b=st->n.b|nSchrit.b&0x80; // Zuklapp-Bits kombinieren
be=st->be0();
type=2;
for (ibe=0;ibe<nBefehl.c;ibe++) {
if (!(nBefehl.f) && !--cnt) return; // nur zählen wenn aufgeklappt
be=be->fin();
}// am Ende dieser Schleife steht der Zeiger auf SCHRIT*
}// am Ende dieser Schleife steht der Zeiger auf REZEPT*
if (!++ire) goto aussen; // weiter mit Rezeptliste (nRezept überspringen)
}// am Ende dieser Schleife steht der Zeiger am Ende der Rezeptliste
type=0xFF; // nicht gefunden! (Bspw. hinter allen Einträgen)
}
// liefert Zeilennummer, 0xFF bei Fehler
// Bei <lastindex> == 0 für .ire (auf Rezept)
// Bei <lastindex> == 1 für .ire und .ist (auf Schritt)
// Da der gesuchte Schritt in einem zugeklappten Rezept liegen kann, gibt's dann 0xFF zurück.
byte ITER::line() {
if (type==0xFF) return -1; // ungültig
byte i;
byte ret=0;
N nSchrit;
initgs(); // früherer Start
if (ire==0xFF) {
if (!showSafe) return -1; // nicht zu sehen
nSchrit.b=1; // (übergeordnetes gedachtes Rezept stets aufgeklappt)
}else{
if (showSafe) ret=st->getLines();
init();
for (i=0;i<ire;i++) { // Rezepte immer zählen, keine Überlaufprüfung!
ret+=re->getLines(); // okkupierte Treeview-Zeilen zählen
re=re->fin(); // zum nächsten Rezept
}
nSchrit=re->n; // Anzahl Aktionen
}
if (type>=1) { // im richtigen Rezept auf Aktion kommen
if (nSchrit.f) return -1; // Rezept nicht aufgeklappt!
ret++; // Rezept-Zeile zählen
st=re->st0(); // Zeiger auf erste Aktion
for (i=0;i<ist;i++) {
ret+=st->getLines();
st=st->fin();
}
if (type>=2) {
if (st->n.f) return -1; // Nicht aufgeklappt!
ret++; // Aktion-Zeile zählen
be=st->be0();
for (i=0;i<ibe;i++) {
ret+=be->getLines(); // liefert zurzeit stets 1
be=be->fin();
}
}
}
return ret;
}
// benutzt nur .ire und .ist um den Zähler zu adressieren,
// der Zeiger wird nicht zerstört
// Liefert eine _Referenz_
N&ITER::getParentCounter(byte level) {
if (ist==0xFF) return eedata.gs.st.n; // Anzahl Sicherheitsaktionen
if (!level) return *(N*)(&rl->nRezept);// Anzahl Rezepte (stets "aufgeklappt")
REZEPT&r=(*rl)[ire]; // (Achtung! operator[])
if (level==1) return r.n; // Anzahl Schritte des Rezepts (wenn auf "Schritt")
return r[ist].n; // Anzahl Einzelaktionen des Schritts (wenn ist==2) (Achtung! operator[])
}
// Liefert den aktuellen Unterknotenzähler als Referenz
N&ITER::getCounter() {
switch (type) {
case 0: return vre->n;
case 1: return vst->n;
default: exit(-5);
}
}
const void*rsbNext(const void*p,byte type) {
switch (type) {
case 2: return ((const BEFEHL*)p)->fin(); //nächster Befehl oder Ende der Kette
case 1: return ((const SCHRIT*)p)->fin(); //nächster Schritt oder Ende der Kette
case 0: return ((const REZEPT*)p)->fin(); //nächstes Rezept oder Ende der Kette
default: exit(-6);
}
}
static ITER riter; // Iterator des Rezet-Editors
// Kanalname als Überschrift ausgeben
static void showName() {
const char*q=NULL;
switch (riter.type) {
case 0: q=F("(Rezept)"); break;
case 1: q=F("(Schritt)"); break;
case 2: {
byte i=riter.be->index;
if (riter.be->operation&3) {
q=FP(Na); // Analogkanäle (max. 256 ohne Gruppen)
}else{
q=(const char*)pgm_read_word(Nlist+(i>>5));
i&=0x1F; // Digitalkanäle (32 pro Gruppe = E/A-Karte)
}
q=strend1(q,i);
}break;
}
centerTitle(q);
}
// Aufruf durch Hintergrundprozess, also idle()
void xorAktionMarker() {
if (!mark) return; // nicht erlaubt
xorMarker(true);
}
static void paintHeader() {
if (!zoom) {
byte nRezept=rl->nRezept;
d.print(floatstr(nRezept),0,0);
d.print(F(" Rezept"));
if (nRezept!=1) d.print('e');
onZoomClear();
showName();
}
}
#define rlines GPIOR1 // Rezept-Zeilenzahl
static byte getLineMax() { // Maximale Zeilennummer
return rlines?rlines-1:0;
}
static void prepaint() {
if (allNew) {
rlines=eedata.gs.getLines();
byte max=getLineMax();
if (C.focusr>max) C.focusr=max;
riter.whereami();
paintHeader();
}
}
/*
Gemeinsame Malroutine für die verschiedenen Editier-Subdialoge
Verwendet die Flags #, reLine, redraw und löscht am Ende alle drei.
newLen reLine redraw Inval Aktion
0 0 0 d.clip nur showFocus
0 0 1 d.clip d.clip restaurieren
0 1 0 - Aktuelle Zeile (C.focusr) malen — muss komplett sichtbar sein!
0 1 1 d.clip [+]/[-] übermalen UND d.clip restaurieren
1 x x - Länge <rlines> setzen und komplett neu malen
*/
static void repaint() {
//auto t=d.T,b=d.B; // Retten für reLine && redraw
byte y=lvScreenY(C.focusr); // passt hier stets ins Byte
if (allNew) {
// Um Kodespeicher zu sparen, ist die Anzahl der darstellbaren
// Programmzeilen auf 254 begrenzt.
// Der EEPROM kann jedoch etwas mehr als diese Zeilenzahl an Kode enthalten.
// Der Anwender muss deshalb einige Knoten geschlossen halten,
// sonst gibt es Darstellungsfehler.
lvInit(C.focusr,getLineMax());
clrAllNew();
goto all;
}
if (reLine) { // [+]/[-] geändert?
if (redraw) {
hideFocus(); // erforderlich weil [+]/[-] Fokusrechteck löchert
d.flags=d.R2_COPYPEN;
d.Y=y;
d.saveClrClip();
plusminus(riter.type?14:6,riter.getCounter()); // [+]/[-] aktualisieren
d.restoreClip();
goto all;
}
d.setClip(lv.L,y,lv.R-lv.SBW,y+lv.dih-1);
goto all; // Aktuelle Zeile komplett neu malen
}
if (redraw) { // Cursor ist bereits weg
all:
ticStart();
d.flags=d.R2_ZERO;
d.fillClip(); // invaliden Bereich löschen
treepaint(); // invaliden Bereich malen
}
clrRedraw();
clrReLine();
showFocus();
}
// Editor-Top+Bottom setzen
static void setTB() {
ed.B=(ed.T=lvScreenY(C.focusr))+lv.dih-1; // unten und oben
ed.cursor=0xFF;
ed.maxlen=5;
ed.setText(sbuf); // markiert alles
}
// Modaler Zeileneditor für ID (eines Rezepts), Zeitdauer (eines Schritts)
// und (analoge oder digitale) Werte (eines Befehls)
// Nie im Zoom-Modus! <action> = gedrückte Taste, die durchgereicht wird
static void dlgValue(byte action=0) {
ed.L=(ed.R=239-lv.SBW)-39; // rechtsbündig am Rollbalken
byte mask;
switch (riter.type) {
case 2: riter.be->sprintVal(false);
mask=riter.be->vlen()?ANALOG::scale[riter.be->index].nk?0x3B:0x2B:0x23;
break;
case 1: floatstr(riter.st->ms10,2); mask=0x33; break; // Dauer: Kein „Minus“
default: floatstr(riter.re->st[0].ms10); mask=0x23; break; // ID: Weder „Minus“ noch „Komma“
}
setTB();
// d.clrClip();
// clearMenu();
// if (C.anapos==1 || !ANALOG::scale[C.focusa].nk) mask&=~0x10; // kein "Komma"
paintMenu(F("\x1F\0\x1E\0\0Minus\0Komma\0\x7F"),mask,0x3F);
// showEditHelp();
if (!action) ed.update();
for(;;action=0) {
if (!action) action=getkey(50); // mit 2 Hz Nullen generieren
switch (action) {
case '\n': { // SET: Wert übernehmen und zurück
switch (riter.type) {
case 0: {
int v=strfloat(ed.text);
if ((unsigned)v>=32767) goto fail; // Inf, NaN und negativ nicht erlaubt
riter.vre->st[0].ms10=v;
}break;
case 1: {
int v=strfloat(ed.text,2);
if ((unsigned)v>=32767) goto fail;
riter.vst->ms10=v;
}break;
case 2: if (!riter.vbe->sscanVal(ed.text)) goto fail; break;
}
}return;
case '\r': return; // Eingaben verwerfen und zurück
default: {
if (action&0x80) action=transcode(action-0xF1);
if (action>=' ') ed.removeLeadingZero(); // Sinnlose führende Null entfernen
if (!ed.handleInput(action)) fail:errorbeep(); continue;
}
}
}
}
// Eingabe einer Kanalnummer eines Befehls (nicht der Wert derselben)
// Bedingung: riter.type==2, zoom==false
static void dlgKanal() {
ed.L=7;
ed.R=24;
paintMenu(F("\x1F\0\x1E\0\x1C\0\x1D\0\0\x7F"));
m1:
paintMenuItem(4*40,F("analog"),riter.be->vlen()?25:24);
m2:
setFocusVis(); // Verhindere Zeichnen des Rahmens in repaint()
repaint();
riter.be->sprintKanal(false);
setTB();
ed.update();
for(;;) {
byte action=getkey(50); // mit 2 Hz Nullen generieren
switch (action) {
case '\n': { // SET: Wert übernehmen
if (!riter.vbe->sscanKanal(ed.text)) goto fail; // Kanalnummer setzen
setReLine();
showName();
}goto m2;
case '\r': return; // Aufrufer muss restaurieren (kein Undo möglich)
case 0xF3:
case 0xF4: {
byte max=(riter.be->vlen()?ANALOG::getMax():DIGITAL::getMax()+1<<3)-1;
byte j=riter.be->index;
do{
j+=action==0xF3?1:-1;
if (j>max) j=action==0xF3?0:max;
}while(!(riter.be->vlen()||shrb(DIGITAL::getAvail(j>>3),j)&1));
riter.vbe->index=j; // neuen Index setzen
setReLine();
showName(); // Name ändert sich!
}goto m2; // Numerische Angabe ändert sich!
case 0xF5: {
if (!riter.vbe->swapAna()) goto fail;
setReLine();
showName(); // Name ändert sich!
}goto m1; // Menü ändert sich!
default: {
if (action&0x80) action=transcode(action-0xF1);
if (!ed.handleInput(action)) fail: errorbeep(); continue;
}
}
}
}
// Scrollt den List-Bereich ab C.focusr nach unten (dy<0) oder oben (dy>0).
// Die Scrollbar wird erneuert. Das Redraw-Flag wird faktisch stets gesetzt.
// Clipping-Rechteck (d.clip) = neu auszumalender Bereich.
static void collapse(char dy) {
d.clrClip();
lv.collapse(lvPixelY(C.focusr),lv.dih*dy);
}
// Neu malen mit Scrollen bei Einfüge- oder Löschposition
// <rlines> wird neu berechnet und anhand der Differenz zu vorher gescrollt
static void insdel() {
auto before=rlines;
rlines=eedata.gs.getLines();
collapse(before-rlines);
}
// 1 Item einfügen, <type> für den Eltern-Zähler (via riter) erforderlich
static bool insert1(byte*pos,word len,byte type) {
N&n=riter.getParentCounter(type); // „auto“ geht hier nicht!!
if ((n.b&0x3F)==0x3F) return false; // Maximalzahl an Rezepten, Schritten oder Befehlen (63) erreicht
if (!eedata.gs.move(pos+len,pos)) return false; // Platz machen und Pointer neu berechnen
n.b++;
ALARM::move(pos+len,pos); // Alarm-IDs (= Befehls-Adressen) verschieben
return true;
}
// Modaler Unterdialog, fragt nach dem Typ des neuen Knotens, und fügt diesen ein.
// Um zu sehen, was gleich passiert, wird entsprechend Platz geschaffen.
// Das Fokusrechteck muss vom Aufrufer entfernt worden sein.
// (Die fokussierte Zeile ist vollständig sichtbar.)
// Aufruf verboten für globale oder lokale Sicherheit,
// denn davor darf kein Schritt eingefügt werden.
static void dlgNeu() {
ITER niter=riter;
byte a=riter.type;
if ((char)a<0) a=1; // Leerzeile? Nur „Rezept“ erlaubt
else{
++C.focusr;
niter.whereami(); // Typ und Zeiger der Folgezeile ermitteln
a=7;
switch (niter.type) {
case 2: a&=4; break; // Folgezeile Befehl: Niemals Schritt oder Rezept einfügen
case 1: a&=6; break; // Folgezeile Schritt: Niemals Rezept einfügen
}
switch (riter.type) {
case 1: if (riter.st->n.b && niter.type!=2) a&=3; break;
// Schritt-Befehl: Befehl erlauben
// Schritt(0)-*: Befehl erlauben
// Schritt-*: Befehl verbieten
case 0: a&=1; // Rezept(0) gibt's nicht, Rezept-Befehl gibt's nicht.
// Rezept-*: Befehl und Schritt verbieten
if (riter.re->n.c==1 && (char)niter.type<=!showSafe) a|=2; break;
// Rezept(1)-Schritt: Schritt erlauben NUR wenn showSafe=0
// Rezept(1)-Rezept, Rezept(1)-nichts: Schritt erlauben
}
}
lv.scrollIntoView(C.focusr); // Folgezeile sichtbar machen wenn unten
collapse(-1); // Platz machen (herunterscrollen)
d.B=d.T+lv.dih-1;
d.fillRect(); // schwarz
d.flags=d.R2_NOTCOPYPEN;
d.print(F("Neu?"),80,d.T); // weiß auf schwarz
d.flags=d.R2_COPYPEN;
paintMenu(F("Rezept\0Schritt\0Befehl"),a,0x3F);
rept:
byte action;
switch (action=getkey()) {
case 0xF1: if (!(a&1)) goto err; break;
case 0xF2: if (!(a&2)) goto err; break;
case 0xF3: if (!(a&4)) goto err; break; // auf Level = 0, 1 oder 2
case '\r': { // Irrtum, keine neue Zeile
d.flags=d.R2_COPYPEN; // weiß ausmalen
collapse(1); // zurückscrollen
if (C.focusr) --C.focusr;
repaint(); // restaurieren
hideFocus();
return; // Aufrufer restauriert Menüzeile
}
default:err:errorbeep(); goto rept; // Alle anderen Tasten (auch Ziffern) anmeckern
}
a=action-0xF1; // 0 = Rezept, 1 = Schritt, 2 = Befehl
if (!insert1(niter.vb,4-a,a)) { // itemlen: 2 Bytes für DigitalIO, 3 für Schritt, 4 für Rezept inklusive Sicherheitsschritt: Klappt so mit Subtraktion!
errorbeep();
return;
}
if (!a) { // neues Rezept? (Sonst Null belassen)
niter.vre->n.b=1; // sofort einen (leeren) Sicherheitsschritt anlegen
*(byte*)&niter.vre->st[0].ms10=ticker.b; // ID (irgendetwas) eintragen
} // Alle anderen neuen Items bleiben mit 0 initialisiert
eedata.gs.calcsizes(); // neu ausrechnen (jetzt erst!)
setAllNew(); // Aufrufer restauriert
}
// Kein Dialog! Rezept, Schritt oder (nicht verwendet:) Befehl (nach hinten) kopieren
static void Duplicate() {
byte*end=(byte*)riter.next();
if (!insert1(end,end-riter.vb,riter.type)) {
errorbeep();
return;
}
memcpy(end,riter.vb,end-riter.vb); // Kopiervorgang
eedata.gs.calcsizes(); // neu ausrechnen (jetzt erst!)
setAllNew(); // Aufrufer restauriert
}
static void Swap(bool down) {
if (!down) {
riter.i[riter.type]--;
C.focusr=riter.line();
}
byte*src=riter.vb;
byte*dst=(byte*)riter.next();
byte*end=(byte*)rsbNext(dst,riter.type);
memrol(src,end-1,dst-src); // Normalerweise müssten jetzt noch die Alarm-IDs verschoben werden: Kein Platz im Flash!
if (down) {
riter.i[riter.type]++;
C.focusr=riter.line();
}
setAllNew();
}
// Modaler Unterdialog, fragt ob wirklich gelöscht werden soll, und löscht den aktuellen Knoten.
// Aufruf verboten für globale oder lokale Sicherheit sowie für die Leerzeile.
static void dlgDelete() {
byte a=riter.type;
if (a!=2) { // Zusätzliche Rückfrage wenn ganze Äste beteiligt sind (TODO: Subitems zählen!)
d.clrClip();
d.flags=d.R2_NOTCOPYPEN;
d.print(F(" Löschen? "),80,lvScreenY(C.focusr)); // weiß auf schwarz über das Item
d.flags=d.R2_COPYPEN;
if (!yesno()) {
setReLine(); // Aufrufer restauriert aktuelle Zeile und Menü
return; // Irrtum, nicht löschen
}
}
riter.getParentCounter().b--; // kann nicht zugeklappt sein (niemals darf 0x80 herauskommen)
byte*src=(byte*)riter.next();
eedata.gs.move(riter.vb,src); // memmove-Quelladresse bestimmen und alles dazwischen löschen
ALARM::move(riter.vb,src); // Alarm-IDs (= Befehls-Adressen) verschieben
eedata.gs.calcsizes(); // neu ausrechnen!
if (C.focusr) --C.focusr; // Zur vorherigen Zeile gehen
setAllNew(); // Aufrufer restauriert alles (Nummerierung und Numerale ändern sich)
}
// Modaler Unterdialog zum Verändern eines Befehls
// sowie zum Anlegen neuer Zeilen und Löschen vorhandener Zeilen.
// Löscht den Bildschirm nicht, sondern setzt auf den von dlgRezept auf.
// Niemals im Zoom-Modus!
static void dlgEdit() {
clrRedraw();
menu:
prepaint();
byte mask=0x37;
if (riter.type!=2) mask=0x30; // „Klausel+“, „Aktion+“ und „BitNr.“ nur für Befehl
// Sonderfall Sicherheitsliste: Kein Einfügen davor, kein Löschen (nur „Dauer“ möglich)
// if (riter.type==1 && !riter.ist) mask=0x08;
// change = 0x3F: Rahmen nicht neu zeichnen
paintMenu(F("Klausel\025\0Aktion\025\0Kanal\0\0Neu\0\x7F"),mask,0x3F);
paintMenuItem(3*40,strend1(F("ID\0Dauer\0Wert"),riter.type));
if (riter.type<2) {
mask=7;
auto i=riter.i[riter.type];
if (i<=riter.type) mask&=~1;// Kein „nach oben tauschen“
if (i>=riter.getParentCounter().b-1) mask&=~2; // Kein „nach unten tauschen“
paintMenu(F("\x1C\x1C\0\x1D\x1D\0× 2"),mask,7);
}
paint:
clrFocusVis(); // Fokus permanent anzeigen lassen
repaint();
byte action;
for(;;) switch (action=getkey()) {
case 0xF1: if (!(mask&1)) goto err; goto gem;
case 0xF2: if (!(mask&2)) goto err; gem:
if (riter.type==2) { // Befehl
byte op=riter.vbe->operation;
if (action==0xF1) {
if (op&7) op=op&0xE0|op+8&0x18; // Analog: Eine von 4 Operationen auswählen (2 sichtbar)
else op^=0x10; // Digital: Eine von 2 Operationen auswählen (keine sichtbar)
}
else op+=0x20; // Eine von 8 Aktionen auswählen
riter.vbe->operation=op;
setReLine();
goto paint;
}
Swap(action-0xF1);
goto menu;
case 0xF3: if (!(mask&4)) goto err;
hideFocus();
if (riter.type==2) { // Befehl: Kanalwahl
dlgKanal();
setReLine(); // Zeile neu malen um Edit-Element zu löschen
goto menu;
}
Duplicate();
goto menu;
case '\n': // Idee für SET: Befehl testen
case 0xF4: action=0;
if (riter.type==2 && !riter.be->vlen()) {
hideFocus();
riter.vbe->operation^=OP_D1^OP_D0; // Sonderfall Binär: Toggle
setReLine();
goto paint;
}goto def;
case 0xF5: { // Neue Zeile einfügen
hideFocus();
dlgNeu();
}goto menu;
case 0xF6: { // Löschen (ganzer Äste!)
if ((char)riter.type<0) goto err; // Markierung auf Leerzeile
if (riter.type==1 && !riter.ist) goto err; // Sicherheitsschritt oder Globale Sicherheit löschen ist verboten
dlgDelete();
}goto menu;
case '\r': {
hideFocus();
}return;
default: def:{
hideFocus();
dlgValue(action);
setReLine();
}goto menu;
err: errorbeep(); continue;
}
}
// Prüft ob [+]/[-] davorgesetzt werden soll:
static bool menuPlusMinus() {
switch (riter.type) {
case 0: if (riter.re->n.c>!showSafe) return true;
case 1: if (riter.st->n.c) return true;
}
return false;
}
static void paintMenuPlusMinus() {
paintMenuItem(2*40,menuPlusMinus()?F("\x1A \x1B"):NULL);
}
// Prüft ob die aktuelle Zeile „Edit“ oder nur „Neu“ zulässt
// (Ein Rezept _vor_ alle anderen Rezepte lässt sich nur
// bei sichtbaren Sicherheitsschritten einfügen.)
static bool menuEdit() {
switch (riter.type) {
case 1: if (!riter.ist) break; // Sicherheitsschritte sind weder editier- noch löschbar
case 0: // Jeder Befehl kann editierbare ID haben und ist löschbar
case 2: return true; // Alle Schritte sind editier- und löschbar
}
return false; // Sicherheitsschritte und die Leerzeile lassen nur „Neu“ zu
}
static void paintMenuEdit() {
// if (riter.type==1 && !riter.ist) goto err; // Kein Editieren Sicherheitsschritt
paintMenuItem(3*40,menuEdit()?F("Edit"):F("Neu"));
}
void dlgRezept() {
clrFocusVis();
vorn: // Bei Änderung von "zoom" und "showSafe" (selten)
// Kompletter Bildaufbau: Kopf, Zeilenzahl, whereami, Liste ...
d.clear();
setAllNew(); // Alles bei repaint() neu malen
// Menü neu zeichnen (nach Sub-Dialog)
menu: // Menü restaurieren (nach Sub-Dialogen)
prepaint();
if (!zoom) {
d.saveClrClip();
paintMenu(F("\x1C\0\x1D"),0x03);
paintMenuPlusMinus();
paintMenuEdit();
paintMenuItem(4*40,F("Safe"),showSafe?25:24); // mit Checkmark
paintMenuZoom();
d.restoreClip();
}
paint: // Neu malen (je nach Flags newLen, redraw und reLine)
repaint();
rept:
// mit 2 Hz Nullen generieren, sonst idle() aufrufen
switch (byte action=getkey(50)) {
case 0: {
if (ticCheck(50)) xorFocus();
}goto rept;
case '\n': switch (riter.type) { // SET = auswählen
case 0: { // Rezept: Aktuelles Rezept festlegen
if (riter.ire==C.runrez) {
if (run.aktion) run.aktion=(run.re->n.c)-1; // Läuft bereits: zu Ende führen!
else run.start(C.runrez); // Ist bereits aktiv: starten!
goto rept;
}
hideFocus();
byte n=riter.ire;
xorMarker();
C.runrez=n;
xorMarker();
}goto rept;
}err: errorbeep(); goto rept;
case '\r': goto exi;
case 0xF1: // hoch
case 0xF2: { // runter
hideFocus();
C.focusr=updown(C.focusr,action,getLineMax());
riter.whereami(); // <riter> neu berechnen
if (!zoom) {
showName(); // neuer Header
paintMenuPlusMinus();
paintMenuEdit();
}
d.clrClip();
lv.scrollIntoView(C.focusr);
}goto paint;
case 0xF3: { // plus/minus
if (!menuPlusMinus()) goto err; // keine Funktion wo kein [+]/[-] steht
hideFocus();
riter.getCounter().b^=0x80;
++C.focusr; // Scrollpunkt dahinter
insdel(); // dadurch neue Länge
--C.focusr;
setReLine(); // [+]/[-] neu malen lassen
}goto paint;
case 0xF4: {
if (zoom) goto err;
hideFocus();
if (menuEdit()) dlgEdit();
else dlgNeu(); // Kann [+]/[-] darüber erzeugen u.v.a.m.!
}goto menu;
case 0xF5: { // Umschalten des showSafe-Bits
xorShowSafe();
byte line=riter.line();
if ((char)line<0) line=0; // kann bei showSafe==0 passieren
C.focusr=line;
}goto vorn;
case 0xF6: xorZoom(); goto vorn;
default: { // Edit
if (zoom) goto err;
hideFocus();
dlgValue(action); // ID, Dauer oder Wert eingeben
clrFocusVis();
setReLine();
}goto menu;
}
exi:
markNo(); // Ende der Darstellung, keine Dreiecke xoren
}
Detected encoding: UTF-8 | 0
|