/* This file is part of * ====================================================== * * LyX, The Document Processor * * Copyright (C) 1995 Matthias Ettrich, * Copyright (C) 1995-1998 The LyX Team. *
*======================================================*/
// this should be static, but I need it in buffer.C bool quitting; // flag, that we are quitting the program externbool finished; // all cleanup done just let it run through now.
char ascii_type; /* for selection notify callbacks */
bool scrolling = false;
char updatetimer = 0;
/* whether the work area should get callbacks */ bool input_prohibited = false;
/* the selection possible is needed, that only motion events are
* used, where the bottom press event was on the drawing area too */ bool selection_possible = false;
void InsertCorrectQuote();
/* This is the inset locking stuff needed for mathed --------------------
an inset can simple call LockInset in it's edit call and *ONLY* in it's edit call. Inset::Edit() can only be called by the main lyx module.
Then the inset may modify the menu's and/or iconbars.
Unlocking is either done by LyX or the inset itself with a UnlockInset-call
During the lock, all button and keyboard events will be modified and send to the inset through the following inset-features. Note that Inset::InsetUnlock will be called from inside UnlockInset. It is meant to contain the code for restoring the menus and things like this.
virtual void InsetButtonPress(int x, int y, int button); virtual void InsetButtonRelease(int x, int y, int button); virtual void InsetKeyPress(XKeyEvent *ev); virtual void InsetMotionNotify(int x, int y, int state); virtual void InsetUnlock();
If a inset wishes any redraw and/or update it just has to call UpdateInset(this). It's is completly irrelevant, where the inset is. UpdateInset will find it in any paragraph in any buffer. Of course the_locking_inset and the insets in the current paragraph/buffer are checked first, so no performance problem should occur. Hope that's ok for the beginning, Alejandro, sorry that I needed so much time,
Matthias
*/
void UpdateInset(Inset* inset, bool mark_dirty = true); /* these functions return 1 if an error occured,
otherwise 0 */ // Now they work only for updatable insets. [Alejandro 080596] int LockInset(UpdatableInset* inset); void ToggleLockedInsetCursor(long x, long y, int asc, int desc); void FitLockedInsetCursor(long x, long y, int asc, int desc); int UnlockInset(UpdatableInset* inset); void LockedInsetStoreUndo(Undo::undo_kind kind);
/* this is for asyncron updating. UpdateInsetUpdateList will be called
automatically from LyX. Just insert the Inset into the Updatelist */ void UpdateInsetUpdateList(); void PutInsetIntoInsetUpdateList(Inset* inset);
unsignedchar GetCurrentTextClass() // Who are we asking? // Shouldn't this question be directed to the buffer? // Indeed it should. Asger.
{ return current_view->currentBuffer()->params.textclass;
}
// How should this actually work? Should it prohibit input in all BufferViews, // or just in the current one? If "just the current one", then it should be // placed in BufferView. If "all BufferViews" then LyXGUI (I think) should // run "ProhibitInput" on all LyXViews which will run prohibitInput on all // BufferViews. Or is it perhaps just the (input in) BufferViews in the // current LyxView that should be prohibited (Lgb) (This applies to // "AllowInput" as well.) void ProhibitInput()
{
input_prohibited = true; if (current_view->getScreen())
current_view->getScreen()->HideCursor();
/* set the cursor to the watch for all forms and the canvas */
XDefineCursor(fl_display, fd_form_main->form_main->window, cursor); if (fd_form_paragraph->form_paragraph->visible)
XDefineCursor(fl_display,
fd_form_paragraph->form_paragraph->window,
cursor); if (fd_form_character->form_character->visible)
XDefineCursor(fl_display,
fd_form_character->form_character->window,
cursor);
XFlush(fl_display);
fl_deactivate_all_forms();
}
// Should find a way to move this into BufferView.C void SetXtermCursor(Window win)
{ static Cursor cursor; staticchar cursor_undefined = 1; if (cursor_undefined){
cursor = XCreateFontCursor(fl_display, XC_xterm);
XFlush(fl_display);
cursor_undefined = 0;
}
XDefineCursor(fl_display, win, cursor);
XFlush(fl_display);
}
void AllowInput()
{
input_prohibited = false;
/* reset the cursor from the watch for all forms and the canvas */
XUndefineCursor(fl_display, fd_form_main->form_main->window); if (fd_form_paragraph->form_paragraph->visible)
XUndefineCursor(fl_display,
fd_form_paragraph->form_paragraph->window); if (fd_form_character->form_character->visible)
XUndefineCursor(fl_display,
fd_form_character->form_character->window); if (current_view->getWorkArea()->belowmouse)
SetXtermCursor(fd_form_main->form_main->window);
XFlush(fl_display);
fl_activate_all_forms();
}
void FreeUpdateTimer()
{ /* a real free timer would be better but I don't know
* how to do this with xforms */
updatetimer = 0;
}
if (!current_view->currentBuffer()->text->selection)
current_view->currentBuffer()->text->sel_cursor =
current_view->currentBuffer()->text->cursor;
if (f==1 || f==-1) { if (current_view->currentBuffer()->isLyxClean()) {
current_view->currentBuffer()->markDirty();
minibuffer->setTimer(4);
} else {
current_view->currentBuffer()->markDirty();
}
}
}
// // Menu callbacks //
// // File menu //
// should be moved to lyxfunc.C void MenuWrite(Buffer* buf)
{
XFlush(fl_display); if (!bufferlist.write(buf)) {
LString fname = buf->getFileName();
LString s = MakeAbsPath(fname); if (AskQuestion(_("Save failed. Rename and try again?"),
MakeDisplayPath(s,50),
_("(If not, document is not saved.)"))) {
MenuWriteAs(buf);
}
} else {
lastfiles->newFile(buf->getFileName());
}
}
// should be moved to BufferView.C void MenuWriteAs(Buffer *buffer)
{ if (!buffer->text) return;
fname = fileDlg.Select(_("Enter Filename to Save Document as"),
OnlyPath(fname), "*.lyx",
OnlyFilename(fname));
AllowInput();
if (fname.empty()) {
minibuffer->Set(_("Canceled.")); return;
}
// Make sure the absolute filename ends with appropriate suffix
LString s= MakeAbsPath(fname); if (!IsLyXFilename(s))
s += ".lyx";
// Same name as we have already? if (s == oldname) { if (!AskQuestion(_("Same name as document already has:"),
MakeDisplayPath(s,50),
_("Save anyway?"))) return; // Falls through to name change and save
} // No, but do we have another file with this name open? elseif (bufferlist.exists(s)) { if (AskQuestion(_("Another document with same name open!"),
MakeDisplayPath(s,50),
_("Replace with current document?")))
{
bufferlist.close(bufferlist.getBuffer(s));
// Ok, change the name of the buffer, but don't save!
buffer->setFileName(s);
buffer->markDirty();
minibuffer->Set(_("Document renamed to '"),
MakeDisplayPath(s),
_("', but not saved..."));
} return;
} // Check whether the file exists else {
FileInfo myfile(s); if (myfile.isOK() && !AskQuestion(_("Document already exists:"),
MakeDisplayPath(s,50),
_("Replace file?"))) return;
}
// Ok, change the name of the buffer
buffer->setFileName(s);
buffer->markDirty(); // And save // Small bug: If the save fails, we have irreversible changed the name // of the document.
MenuWrite(buffer);
}
#if 0 externbool gsworking(); #endif
int MenuRunLaTeX(Buffer *buffer)
{ int ret;
#if 0 if (gsworking()) {
WriteAlert(_("Sorry, can't do this while pictures are being rendered."),
_("Please wait a few seconds for this to finish and try again."),
_("(or kill runaway gs processes by hand and try again.)")); return 1;
} extern pid_t isp_pid; // from spellchecker.C if(isp_pid != -1)
{
WriteAlert(_("Can't do this while the spellchecker is running."),
_("Stop the spellchecker first.")); return 1;
} #endif
if (buffer->isLinuxDoc())
ret = RunLinuxDoc(1, buffer->getFileName()); else
ret = buffer->runLaTeX();
if (ret > 0) {
LString s;
LString t; if (ret == 1) {
s = _("One error detected");
t = _("You should try to fix it.");
} else {
s += ret;
s += _(" errors detected.");
t = _("You should try to fix them.");
}
WriteAlert(_("There were errors during the LaTeX run."), s, t);
} return ret;
}
int MenuRunChktex(Buffer *buffer)
{ int ret;
if (buffer->isLinuxDoc()) {
WriteAlert(_("Chktex does not work with LinuxDoc.")); return 0;
} else
ret = buffer->runChktex();
if (ret >= 0) {
LString s;
LString t; if (ret == 0) {
s = _("No warnings found.");
} elseif (ret == 1) {
s = _("One warning found.");
t = _("Use 'Edit->Go to Error' to find it.");
} else {
s += ret;
s += _(" warnings found.");
t = _("Use 'Edit->Go to Error' to find them.");
}
WriteAlert(_("Chktex run successfully"), s, t);
} else {
WriteAlert(_("Error!"),_("It seems chktex does not work."));
} return ret;
}
int MakeDVIOutput(Buffer *buffer)
{ if (!(buffer->text)) return 1;
int ret = 0;
LString path = OnlyPath(buffer->getFileName()); if (lyxrc->use_tempdir || (IsDirWriteable(path) < 1)) {
path = buffer->tmppath;
} if (!buffer->isDviClean()) {
PathPush(path);
ret = MenuRunLaTeX(buffer);
PathPop();
} return ret;
}
/* wait == false means don't wait for termination */ /* wait == true means wait for termination */ // The bool should be placed last on the argument line. (Lgb) // Returns false if we fail. bool RunScript(Buffer *buffer, bool wait,
LString const & command, LString const & orgname = LString(), bool need_shell=true)
{
LString path;
LString cmd;
LString name= orgname; int result;
if (MakeDVIOutput(buffer) > 0) returnfalse; /* get DVI-Filename */ if (name.empty())
name = ChangeExtension(buffer->getFileName(), ".dvi", true);
if (need_shell) { #ifndef __EMX__ if (!wait)
cmd += " &"; #else // OS/2 cmd.exe has another use for '&' if (!wait) {
LString sh = getenv ("EMXSHELL"); if (sh.empty())
sh = getenv ("COMSPEC"); if (sh.contains("cmd.exe"))
cmd = "start /min/n " + cmd; else
cmd += " &";
} #endif // It seems that, if wait is false, we never get back // the return code of the command. This means that all // the code I added in PrintApplyCB is currently // useless... #ifdef WITH_WARNINGS #warning What should we do here? #endif
minibuffer->Set(_("Executing command:"), cmd);
result = one.Startscript(Systemcalls::System, cmd);
} else {
minibuffer->Set(_("Executing command:"), cmd);
result = one.Startscript(wait ? Systemcalls::Wait
: Systemcalls::DontWait, cmd);
}
PathPop(); return (result==0);
}
// Returns false if we fail bool MenuRunDvips(Buffer *buffer, bool wait=false)
{ if (!buffer->text) returnfalse;
switch (real_papersize) { case PAPER_USLETTER:
paper = "letter"; break; case PAPER_A3PAPER:
paper = "a3"; break; case PAPER_A4PAPER:
paper = "a4"; break; case PAPER_A5PAPER:
paper = "a5"; break; case PAPER_B5PAPER:
paper = "b5"; break; case PAPER_EXECUTIVEPAPER:
paper = "foolscap"; break; case PAPER_LEGALPAPER:
paper = "legal"; break; default: /* If nothing else fits, keep an empty value... */ break;
}
// Make postscript file.
LString command = "dvips -o " + SpaceLess(ps); // dvips won't accept -t letter -t landscape. In all other // cases, include the paper size explicitly. if (!paper.empty()
&& (real_papersize != PAPER_USLETTER ||
buffer->params.orientation == ORIENTATION_PORTRAIT))
command += " -t " + paper; if (buffer->params.orientation == ORIENTATION_LANDSCAPE)
command += " -t landscape"; // push directorypath, if necessary
LString path = OnlyPath(buffer->getFileName()); if (lyxrc->use_tempdir || (IsDirWriteable(path) < 1)){
path = buffer->tmppath;
}
PathPush(path); bool ret = RunScript(buffer, wait, command);
AllowInput();
PathPop(); return ret;
}
// Returns false if we fail bool MenuPreviewPS(Buffer *buffer)
{ if (!buffer->text) returnfalse;
// Generate postscript file if (!MenuRunDvips(buffer, true)) { returnfalse;
}
switch (real_papersize) { case PAPER_USLETTER:
paper = "us"; break; case PAPER_A3PAPER:
paper = "a3"; break; case PAPER_A4PAPER:
paper = "a4"; break; case PAPER_A5PAPER:
paper = "a5"; break; case PAPER_B5PAPER:
paper = "b5"; break; case PAPER_EXECUTIVEPAPER:
paper = "foolscap"; break; case PAPER_LEGALPAPER:
paper = "legal"; break; default: /* If nothing else fits, keep the empty value */ break;
}
if (paper.empty()) { if (buffer->params.orientation == ORIENTATION_LANDSCAPE) // we HAVE to give a size when the page is in // landscape, so use USletter.
paper = " -paper usr";
} else {
paper = " -paper " + paper; if (buffer->params.orientation == ORIENTATION_LANDSCAPE)
paper+='r';
}
// push directorypath, if necessary
LString path = OnlyPath(buffer->getFileName()); if (lyxrc->use_tempdir || (IsDirWriteable(path) < 1)){
path = buffer->tmppath;
}
PathPush(path); // Run dvi-viewer
LString command = lyxrc->view_dvi_command + paper ; bool ret = RunScript(buffer, false, command);
PathPop(); return ret;
}
void MenuMakeLaTeX(Buffer *buffer)
{ if (buffer->text) { // Get LaTeX-Filename
LString s = SpaceLess(ChangeExtension(
buffer->getFileName(), ".tex", false));
FilePtr myfile(s, FilePtr::read); if (myfile() &&
!AskQuestion(_("File already exists:"),
MakeDisplayPath(s,50),
_("Do you want to overwrite the file?"))) {
minibuffer->Set(_("Canceled")); return;
}
// Set a flag that we do quitting from the program, // so no refreshes are necessary.
quitting = true;
// close buffers first
bufferlist.closeAll();
// do any other cleanup procedures now
lyxerr.debug("Deleting tmp dir " + system_tempdir);
DestroyLyXTmpDir(system_tempdir);
finished = true;
}
void AutoSave() // should probably be moved into BufferList (Lgb) // Perfect target for a thread...
{ if (!current_view->getScreen() || !current_view->available()) return;
if (current_view->currentBuffer()->isBakClean()
|| current_view->currentBuffer()->isReadonly()) { // We don't save now, but we'll try again later
current_view->getOwner()->resetAutosaveTimer(); return;
}
minibuffer->Set(_("Autosaving current document..."));
// tmp_ret will be located (usually) in /tmp // will that be a problem?
LString tmp_ret = tmpnam(NULL);
pid_t pid = fork(); // If you want to debug the autosave // you should set pid to -1, and comment out the // fork. if (pid == 0 || pid == -1) { // pid = -1 signifies that lyx was unable // to fork. But we will do the save // anyway. bool failed = false; if (!tmp_ret.empty()) {
current_view->currentBuffer()->writeFile(tmp_ret, 1); // assume successful write of tmp_ret if (rename(tmp_ret.c_str(), fname.c_str()) == -1) {
failed = true; // most likely couldn't move between filesystems // unless write of tmp_ret failed // so remove tmp file (if it exists)
remove(tmp_ret.c_str());
}
} else {
failed = true;
}
if (failed) { // failed to write/rename tmp_ret so try writing direct if (!current_view->currentBuffer()->writeFile(fname,
1)) { // It is dangerous to do this in the child, // but safe in the parent, so... if (pid == -1)
minibuffer->Set(_("Autosave Failed!"));
}
} if (pid == 0) { // we are the child so...
_exit(0);
}
}
fl_set_counter_value(fd_form_document->slider_secnumdepth,
params->secnumdepth);
fl_set_counter_value(fd_form_document->slider_tocdepth,
params->tocdepth); if (!params->float_placement.empty()) { // buffer local (Lgb)
fl_set_input(fd_form_document->input_float_placement,
params->float_placement.c_str());
} else {
fl_set_input(fd_form_document->input_float_placement, LString("").c_str());
} if (!params->options.empty())
fl_set_input(fd_form_document->input_extra,
params->options.c_str()); else
fl_set_input(fd_form_document->input_extra,
LString("").c_str());
if (current_view->currentBuffer()->isLinuxDoc()) { // bullets not used in LinuxDoc
fl_deactivate_object(fd_form_document->button_bullets);
fl_set_object_lcol(fd_form_document->button_bullets,
FL_INACTIVE);
} else {
fl_activate_object(fd_form_document->button_bullets);
fl_set_object_lcol(fd_form_document->button_bullets,
FL_BLACK);
}
if (current_view->currentBuffer()->isReadonly()) {
DisableDocumentLayout();
} else {
EnableDocumentLayout();
}
void MenuLayoutSave()
{ if (!current_view->getScreen() || ! current_view->available()) return;
if (AskQuestion(_("Do you want to save the current settings"),
_("for Character, Document, Paper and Quotes"),
_("as default for new documents?")))
current_view->currentBuffer()->saveParamsAsDefaults();
}
/* callbacks added for LinuxDoc support (Pascal André) : * - marks and cross references TeX macros
*/
/* -------> Returns the current font and depth by printing a message. In the * future perhaps we could try to implement a callback to the button-bar. * That is, `light' the bold button when the font is currently bold, etc.
*/
LString CurrentState()
{
LString state; if (current_view->available()) { // I think we should only show changes from the default // font. (Asger)
Buffer * buffer = current_view->currentBuffer();
LyXFont font = buffer->text->real_current_font;
LyXFont defaultfont = lyxstyle.TextClass(buffer->
params.textclass)->defaultfont;
font.reduce(defaultfont);
state = _("Font: ") + font.stateText();
int depth = buffer->text->GetDepth(); if (depth>0)
state += LString(_(", Depth: ")) + depth;
} return state;
}
/* -------> Does the actual toggle job of the XxxCB() calls above. * Also shows the current font state.
*/ static void ToggleAndShow(LyXFont const & font)
{ if (current_view->available()) {
current_view->getScreen()->HideCursor();
current_view->currentBuffer()->update(-2); bool toggleall = fl_get_button(fd_form_character->check_toggle_all);
current_view->currentBuffer()->text->ToggleFree(font, toggleall);
current_view->currentBuffer()->update(1);
} // removed since it overrides the ToggleFree Message about the style // Since Styles are more "High Level" than raw fonts I think the user // prefers it like this Matthias // FontStateShowCB( 0, 0 );
}
void CopyEnvironmentCB()
{ if (current_view->available()) {
current_view->currentBuffer()->text->copyEnvironmentType(); /* clear the selection, even if mark_set */
current_view->getScreen()->ToggleSelection();
current_view->currentBuffer()->text->ClearSelection();
current_view->currentBuffer()->update(-2);
minibuffer->Set(_("Paragraph environment type copied"));
}
}
void PasteEnvironmentCB()
{ if (current_view->available()) {
current_view->currentBuffer()->text->pasteEnvironmentType();
minibuffer->Set(_("Paragraph environment type set"));
current_view->currentBuffer()->update(1);
}
}
void CopyCB()
{ if (current_view->available()) {
current_view->currentBuffer()->text->CopySelection(); /* clear the selection, even if mark_set */
current_view->getScreen()->ToggleSelection();
current_view->currentBuffer()->text->ClearSelection();
current_view->currentBuffer()->update(-2);
minibuffer->Set(_("Copy"));
}
}
// Change environment depth. // if decInc == 0, depth change taking mouse button number into account // if decInc == 1, increment depth // if decInc == -1, decrement depth void DepthCB(FL_OBJECT *ob, long decInc)
{ int button = 1;
/* When decInc != 0, fake a mouse button. This allows us to
implement depth-plus and depth-min commands. RVDK_PATCH_5. */ /* check out wether ob is defined, too (Matthias) */ if ( decInc < 0 )
button = 0; elseif (!decInc && ob) {
button = fl_get_button_numb(ob);
}
if (current_view->available()) {
current_view->getScreen()->HideCursor();
current_view->currentBuffer()->update(-2); if (button == 1)
current_view->currentBuffer()->text->IncDepth(); else
current_view->currentBuffer()->text->DecDepth();
current_view->currentBuffer()->update(1);
minibuffer->Set(_("Changed environment depth" " (in possible range, maybe not)"));
}
}
// This is both GUI and LyXFont dependent. Don't know where to put it. (Asger) // Well, it's mostly GUI dependent, so I guess it will stay here. (Asger)
LyXFont UserFreeFont()
{
LyXFont font(LyXFont::ALL_IGNORE); int pos;
pos = fl_get_choice(fd_form_character->choice_family); switch(pos) { case 1: font.setFamily(LyXFont::IGNORE_FAMILY); break; case 2: font.setFamily(LyXFont::ROMAN_FAMILY); break; case 3: font.setFamily(LyXFont::SANS_FAMILY); break; case 4: font.setFamily(LyXFont::TYPEWRITER_FAMILY); break; case 5: font.setFamily(LyXFont::INHERIT_FAMILY); break;
}
pos = fl_get_choice(fd_form_character->choice_series); switch(pos) { case 1: font.setSeries(LyXFont::IGNORE_SERIES); break; case 2: font.setSeries(LyXFont::MEDIUM_SERIES); break; case 3: font.setSeries(LyXFont::BOLD_SERIES); break; case 4: font.setSeries(LyXFont::INHERIT_SERIES); break;
}
pos = fl_get_choice(fd_form_character->choice_shape); switch(pos) { case 1: font.setShape(LyXFont::IGNORE_SHAPE); break; case 2: font.setShape(LyXFont::UP_SHAPE); break; case 3: font.setShape(LyXFont::ITALIC_SHAPE); break; case 4: font.setShape(LyXFont::SLANTED_SHAPE); break; case 5: font.setShape(LyXFont::SMALLCAPS_SHAPE); break; case 6: font.setShape(LyXFont::INHERIT_SHAPE); break;
}
pos = fl_get_choice(fd_form_character->choice_size); switch(pos) { case 1: font.setSize(LyXFont::IGNORE_SIZE); break; case 2: font.setSize(LyXFont::SIZE_TINY); break; case 3: font.setSize(LyXFont::SIZE_SCRIPT); break; case 4: font.setSize(LyXFont::SIZE_FOOTNOTE); break; case 5: font.setSize(LyXFont::SIZE_SMALL); break; case 6: font.setSize(LyXFont::SIZE_NORMAL); break; case 7: font.setSize(LyXFont::SIZE_LARGE); break; case 8: font.setSize(LyXFont::SIZE_LARGER); break; case 9: font.setSize(LyXFont::SIZE_LARGEST); break; case 10: font.setSize(LyXFont::SIZE_HUGE); break; case 11: font.setSize(LyXFont::SIZE_HUGER); break; case 12: font.setSize(LyXFont::INCREASE_SIZE); break; case 13: font.setSize(LyXFont::DECREASE_SIZE); break; case 14: font.setSize(LyXFont::INHERIT_SIZE); break;
}
pos = fl_get_choice(fd_form_character->choice_bar); switch(pos) { case 1: font.setEmph(LyXFont::IGNORE);
font.setUnderbar(LyXFont::IGNORE);
font.setNoun(LyXFont::IGNORE);
font.setLatex(LyXFont::IGNORE); break; case 2: font.setEmph(LyXFont::TOGGLE); break; case 3: font.setUnderbar(LyXFont::TOGGLE); break; case 4: font.setNoun(LyXFont::TOGGLE); break; case 5: font.setLatex(LyXFont::TOGGLE); break; case 6: font.setEmph(LyXFont::INHERIT);
font.setUnderbar(LyXFont::INHERIT);
font.setNoun(LyXFont::INHERIT);
font.setLatex(LyXFont::INHERIT); break;
}
pos = fl_get_choice(fd_form_character->choice_color); switch(pos) { case 1: font.setColor(LyXFont::IGNORE_COLOR); break; case 2: font.setColor(LyXFont::NONE); break; case 3: font.setColor(LyXFont::BLACK); break; case 4: font.setColor(LyXFont::WHITE); break; case 5: font.setColor(LyXFont::RED); break; case 6: font.setColor(LyXFont::GREEN); break; case 7: font.setColor(LyXFont::BLUE); break; case 8: font.setColor(LyXFont::CYAN); break;
--> --------------------
--> maximum size reached
--> --------------------
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.42Angebot
Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.