/*
 * KTurtle, Copyright (C) 2003-04 Cies Breijs <cies # kde ! nl>

    This program is free software; you can redistribute it and/or
    modify it under the terms of version 2 of the GNU General Public
    License as published by the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */


// BEGIN includes and defines

#include <stdlib.h>

#include <qbutton.h>
#include <qregexp.h>
#include <qpainter.h>
#include <qtooltip.h>
#include <qtimer.h>
#include <qwhatsthis.h>

#include <kapplication.h>
#include <kconfigdialog.h>
#include <kdebug.h>
#include <kedittoolbar.h>
#include <kfiledialog.h>
#include <kimageio.h>
#include <kinputdialog.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kmenubar.h>
#include <kprinter.h>
#include <ksavefile.h>
#include <kstatusbar.h>

#include <ktexteditor/clipboardinterface.h>
#include <ktexteditor/cursorinterface.h>
#include <ktexteditor/editorchooser.h>
#include <ktexteditor/editinterface.h>
#include <ktexteditor/highlightinginterface.h>
#include <ktexteditor/printinterface.h>
#include <ktexteditor/selectioninterface.h>
#include <ktexteditor/undointerface.h>
#include <ktexteditor/viewcursorinterface.h>

#include "lexer.h"
#include "settings.h"
#include "translate.h"

#include "kturtle.h"

// StatusBar field IDs
#define IDS_STATUS      0
#define IDS_LINECOLUMN  2
#define IDS_INS         3
#define IDS_LANG        4

// END



// BEGIN constructor and destructor

MainWindow::MainWindow(KTextEditor::Document *document) : editor(0)
{
	// the initial values
	CurrentFile = KURL(); // fill with empty URL
	setCaption( i18n("Untitled") );
	picker = 0; // for the colorpickerdialog
	executing = false;
	b_fullscreen = false;

	// set the shell's ui resource file
	if (!document)
	{
		if ( !(document = KTextEditor::EditorChooser::createDocument(0,"KTextEditor::Document") ) )
		{
			KMessageBox::error( this, i18n("A KDE text-editor component could not be found;\n"
			                               "please check your KDE installation.") );
			kapp->exit(1);
		}
		// docList.append(doc);
	}
	doc = document;

	setupCanvas();
	setupEditor();

	setupStatusBar();
	setupActions();
	createShellGUI(true);
	setMinimumSize(200,200);

	// init with more usefull size, stolen from kwite (they stole it from konq)
	if ( !initialGeometrySet() && !kapp->config()->hasGroup("MainWindow Settings") ) resize(640, 480);

	KConfig *config = kapp->config();
	readConfig(config);
}

MainWindow::~MainWindow()
{
	delete editor->document();
}

// END



// BEGIN setup's (actions, editor, statusbar, canvas)

void MainWindow::setupActions()
{
	KActionCollection *ac = actionCollection(); // abbreviation
	
	// File actions
	KStdAction::openNew(this, SLOT(slotNewFile()), ac);
	openExAction = new KAction(i18n("Open Exa&mples..."), "bookmark_folder", CTRL+Key_E, this, SLOT(slotOpenExample()), ac, "open_examples");
	KStdAction::open(this, SLOT(slotOpenFile()), ac);
	m_recentFiles = KStdAction::openRecent(this, SLOT(slotOpenFile(const KURL&)), ac);
	KStdAction::save(this, SLOT(slotSaveFile()), ac);
	KStdAction::saveAs(this, SLOT(slotSaveAs()), ac);
	new KAction(i18n("Save &Canvas..."), 0, 0, this, SLOT(slotSaveCanvas()), ac, "save_canvas");
	speed = new KSelectAction(i18n("Execution Speed"), 0, ALT+Key_S, this, SLOT( slotChangeSpeed() ), ac, "speed");
	QStringList speeds; speeds << i18n("Full Speed") << i18n("Slow") << i18n("Slower") << i18n("Slowest");
	speed->setItems(speeds);
	speed->setCurrentItem(0);
	run = new KAction(i18n("&Execute Commands"), "gear", ALT+Key_Return, this, SLOT( slotExecute() ), ac, "run");
	pause = new KToggleAction(i18n("Pause E&xecution"), "player_pause", Key_Pause, this, SLOT( slotPauseExecution() ), ac, "pause");
	pause->setChecked(false);
	pause->setEnabled(false);
	stop = new KAction(i18n("Stop E&xecution"), "stop", Key_Escape, this, SLOT( slotAbortExecution() ), ac, "stop");
	stop->setEnabled(false);
	KStdAction::print(this, SLOT(slotPrint()), ac);
	KStdAction::quit(this, SLOT(close()), ac);
	
	// Edit actions
	KStdAction::undo(this, SLOT(slotUndo()), ac);
	KStdAction::redo(this, SLOT(slotRedo()), ac);
	KStdAction::cut(this, SLOT(slotCut()), ac);
	KStdAction::copy(this, SLOT(slotCopy()), ac);
	KStdAction::paste(this, SLOT(slotPaste()), ac);
	KStdAction::selectAll(this, SLOT(slotSelectAll()), ac);
	KStdAction::deselect(this, SLOT(slotClearSelection()), ac);
	new KToggleAction(i18n("Toggle Insert"), Key_Insert, this, SLOT(slotToggleInsert()), ac, "set_insert");
	KStdAction::find(this, SLOT(slotFind()), ac);
	KStdAction::findNext(this, SLOT(slotFindNext()), ac);
	KStdAction::findPrev(this, SLOT(slotFindPrevious()), ac);
	KStdAction::replace(this, SLOT(slotReplace()), ac);
	
	// View actions
	new KToggleAction(i18n("Show &Line Numbers"), 0, Key_F11, this, SLOT(slotToggleLineNumbers()), ac, "line_numbers");
	m_fullscreen = KStdAction::fullScreen(this, SLOT(slotToggleFullscreen()), ac, this, "full_screen");
	m_fullscreen->setChecked(b_fullscreen);

	// Tools actions
	colorpicker = new KToggleAction(i18n("&Color Picker"), "colorize", ALT+Key_C, this, SLOT(slotColorPicker()), ac, "color_picker");
	new KAction(i18n("&Indent"), "indent", CTRL+Key_I, this, SLOT(slotIndent()), ac, "edit_indent");
	new KAction(i18n("&Unindent"), "unindent", CTRL+SHIFT+Key_I, this, SLOT(slotUnIndent()), ac, "edit_unindent");
	new KAction(i18n("Cl&ean Indentation"), 0, 0, this, SLOT(slotCleanIndent()), ac, "edit_cleanIndent");
	new KAction(i18n("Co&mment"), 0, CTRL+Key_D, this, SLOT(slotComment()), ac, "edit_comment");
	new KAction(i18n("Unc&omment"), 0, CTRL+SHIFT+Key_D, this, SLOT(slotUnComment()), ac, "edit_uncomment");
	
	// Settings actions
	KStdAction::preferences( this, SLOT(slotSettings()), ac );
	new KAction(i18n("&Configure Editor..."), "configure", 0, this, SLOT(slotEditor()), ac, "set_confdlg");
	
	// Help actions
	ContextHelp = new KAction(0, 0, Key_F2, this, SLOT(slotContextHelp()), ac, "context_help");
	slotContextHelpUpdate(); // this sets the label of this action
	
	// other
	setXMLFile("kturtleui.rc");
	setupGUI();
}

void MainWindow::setupEditor()
{
	editorDock = new QDockWindow(this);
	editorDock->setNewLine(true);
	editorDock->setFixedExtentWidth(250);
	editorDock->setFixedExtentHeight(150);
	editorDock->setResizeEnabled(true);
	editorDock->setFrameShape(QFrame::ToolBarPanel);
	QWhatsThis::add( editorDock, i18n( "This is the code editor, here you type the Logo commands to instruct the turtle. You can also open an existing Logo program with File->Open Examples... or File->Open." ) );
	moveDockWindow(editorDock, Qt::DockLeft);
	editor = doc->createView (editorDock, 0L);
	// editorInterface is the editor interface which allows us to access the text in the part
	editorInterface = dynamic_cast<KTextEditor::EditInterface*>(doc);
	editorDock->setWidget(editor);

	// default the highlightstyle to "logo" using the needed i18n
	kdDebug(0)<<"The HighlightStyle for the Code Editor: "<<Settings::logoLanguage()<<endl;
	slotSetHighlightstyle( Settings::logoLanguage() );

	// allow the cursor position to be indicated in the statusbar
	connect( editor, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorStatusBar()) );
	// and update the context help menu item
	connect( editor, SIGNAL(cursorPositionChanged()), this, SLOT(slotContextHelpUpdate()) );
	
	translate = new Translate();
}

void MainWindow::setupStatusBar()
{
	statusBar()->insertItem("", IDS_STATUS, 1, false);
	statusBar()->setItemAlignment(IDS_STATUS, AlignLeft);
	statusBar()->insertItem("", IDS_LANG, 0, true);
	statusBar()->insertItem("", IDS_LINECOLUMN, 0, true);
	statusBar()->insertItem("", IDS_INS, 0, true);
	
	// fill the statusbar
	slotStatusBar(i18n("Welcome to KTurtle..."),  IDS_STATUS); // the message part
	slotStatusBar(i18n("Line: %1 Column: %2").arg(1).arg(1), IDS_LINECOLUMN);
	slotStatusBar(i18n("INS"), IDS_INS);
}

void MainWindow::setupCanvas()
{
	baseWidget = new QWidget(this);
	setCentralWidget(baseWidget);
	baseLayout = new QGridLayout(baseWidget, 0, 0);
	canvasView = new Canvas(baseWidget);
	baseLayout->addWidget(canvasView, 0, 0, AlignCenter);
	baseLayout->setRowStretch(0, 1); // this apperntly fixes a pre-usefull scrollbars bug
	baseLayout->setColStretch(0, 1);
	QWhatsThis::add( canvasView, i18n("This is the canvas, here the turtle draws a picture.") );
	canvasView->show();
	connect( canvasView, SIGNAL( CanvasResized() ), this, SLOT( slotUpdateCanvas() ) );
}

// END



// BEGIN staturbar related functions

void MainWindow::slotStatusBar(QString text, int id)
{
	text = " " + text + " "; // help the layout
	statusBar()->changeItem(text, id);
}

void MainWindow::slotCursorStatusBar()
{
	uint cursorLine;
	uint cursorCol;
	dynamic_cast<KTextEditor::ViewCursorInterface*>(editor)->cursorPositionReal(&cursorLine, &cursorCol);
	QString linenumber = i18n(" Line: %1 Column: %2 ").arg(cursorLine + 1).arg(cursorCol + 1);
	statusBar()->changeItem(linenumber, IDS_LINECOLUMN);
}

// END



// BEGIN file menu related fuctions (new, open, save()as, image, print, quit)

void MainWindow::slotNewFile()
{
	if ( !editor->document()->isModified() && CurrentFile.isEmpty() ) return; // do nothing when nothing can be done
	if ( editor->document()->isModified() )
	{
		int result = KMessageBox::warningContinueCancel( this,
		i18n("The program you are currently working on is not saved. "
		     "By continuing you may lose the changes you have made."),
		i18n("Unsaved File"), i18n("&Discard Changes") );
		if (result != KMessageBox::Continue) return;
	}
	editorInterface->clear(); // clear the editor
	canvasView->slotClear(); // clear the view
	editor->document()->setModified(false);
	CurrentFile = KURL();
	setCaption( i18n("Untitled") );
	slotStatusBar(i18n("New file... Happy coding!"), IDS_STATUS);
}



void MainWindow::slotOpenFile(const KURL &urlRef)
{
	KURL url = urlRef;
	if ( url.isEmpty() )
	{
		url = KFileDialog::getOpenURL( QString(":logo_dir"), QString("*.logo|") + i18n("Logo Files"), this, i18n("Open Logo File") );
	}
	loadFile(url);
}

void MainWindow::slotOpenExample()
{
	KURL url;
	url.setPath( locate("data", "kturtle/examples/" + Settings::logoLanguage() + "/") );
	url = KFileDialog::getOpenURL( url.path(), QString("*.logo|") + i18n("Logo Examples Files"), this, i18n("Open Logo Example File") );
	loadFile(url);
}

void MainWindow::loadFile(const KURL &url)
{
	if ( !url.isEmpty() )
	{
		QFile file( url.path() );
		if ( file.open(IO_ReadOnly) )
		{
			if ( editor->document()->isModified() )
			{
				int result = KMessageBox::warningContinueCancel( this,
						i18n("The program you are currently working on is not saved. "
						     "By continuing you may lose the changes you have made."),
						i18n("Unsaved File"), i18n("&Discard Changes") );
				if (result != KMessageBox::Continue)
				{
					slotStatusBar(i18n("Opening aborted, nothing opened."),  IDS_STATUS);
					return;
				}
			}
			QTextStream stream(&file);
			stream.setEncoding(QTextStream::UnicodeUTF8);
			editorInterface->setText( stream.read() );
			file.close();
			m_recentFiles->addURL(url);
			setCaption( url.fileName() );
			slotStatusBar(i18n("Opened file: %1").arg( url.fileName() ), IDS_STATUS);
			editor->document()->setModified(false);
			CurrentFile = url;
			return;
		}
		else
		{
			KMessageBox::error( this,
				i18n("KTurtle was unable to open: \n%1.").arg( url.prettyURL() ),
				i18n("Open Error") );
			slotStatusBar(i18n("Opening aborted because of error."),  IDS_STATUS);
			return;
		}
	}
	slotStatusBar(i18n("Opening aborted."),  IDS_STATUS); // fallback
}

void MainWindow::slotSaveFile()
{
	writeFile(CurrentFile);
}

void MainWindow::slotSaveAs()
{
	KURL url;
	while (true)
	{
		url = KFileDialog::getSaveURL( QString(":logo_dir"), QString("*.logo|") + i18n("Logo Files"), this, i18n("Save As") );
		if ( url.isEmpty() ) // cancelled the save dialog
		{
			slotStatusBar(i18n("Saving aborted."),  IDS_STATUS);
			return;
		}
		if ( QFile( url.path() ).exists() )
		{
			int result = KMessageBox::warningContinueCancel ( this,
					i18n("A program named \"%1\" already exists in this folder. "
							"Do you want to overwrite it?").arg( url.fileName() ),
					i18n("Overwrite?"), i18n("&Overwrite") );
			if (result != KMessageBox::Continue) return;
		}
		break;
	}
	writeFile(url);
}

void MainWindow::writeFile(const KURL &url)
{
	if ( url.isEmpty() ) slotSaveAs();
	else
	{
		QString mString = editorInterface->text(); // get the text
		editorInterface->setText( mString.utf8() ); // convert it to utf8
		editor->document()->saveAs(url); // use the KateParts method for saving
		loadFile(url); // reload the file as utf8 otherwise display weird chars
		setCaption( url.fileName() );
		slotStatusBar(i18n("Saved to: %1").arg( url.fileName() ),  IDS_STATUS);
		m_recentFiles->addURL(url);
		editor->document()->setModified(false);
		CurrentFile = url;
	}
}



void MainWindow::slotSaveCanvas()
{
	KURL url;
	while (true)
	{
		url = KFileDialog::getSaveURL( QString(":logo_dir"), QString("*.png|") +
		i18n("Pictures"), this, i18n("Save Canvas as Picture") );
		if ( url.isEmpty() ) return; // when cancelled the KFiledialog?
		if ( QFile( url.path() ).exists() )
		{
			int result = KMessageBox::warningContinueCancel( this,
				i18n("A picture named \"%1\" already in this folder. "
				     "Do you want to overwrite it?").arg( url.fileName() ),
				i18n("Overwrite?"), i18n("&Overwrite") );
			if (result != KMessageBox::Continue) return;
		}
		break;
	}
	
	QString type( KImageIO::type( url.path() ) );
	if ( type.isNull() ) type = "PNG";
	bool ok = false;
	QPixmap* pixmap = canvasView->canvas2Pixmap();
	if ( url.isLocalFile() )
	{
		KSaveFile saveFile( url.path() );
		if ( saveFile.status() == 0 )
		{
			if ( pixmap->save( saveFile.file(), type.latin1() ) ) ok = saveFile.close();
		}
	}
	if (!ok)
	{
		kdWarning() << "KTurtle was unable to save the canvas drawing" << endl;
		KMessageBox::error(this,
			i18n("KTurtle was unable to save the image to: \n%1.").arg( url.prettyURL() ),
			i18n("Unable to Save Image") );
		slotStatusBar(i18n("Could not save image."),  IDS_STATUS);
		return;
	}
	slotStatusBar(i18n("Saved canvas to: %1").arg( url.fileName() ),  IDS_STATUS);
}



void MainWindow::slotPrint()
{
	int result = KMessageBox::questionYesNoCancel( this,
	i18n("Do you want to print the Logo code or the canvas?"),
	i18n("What to Print?"), i18n("Print &Logo Code"), i18n("Print &Canvas") );
	if (result == KMessageBox::Yes)
	{
		dynamic_cast<KTextEditor::PrintInterface*>(doc)->printDialog();
		return;
	}
	if (result == KMessageBox::No)
	{
		KPrinter printer;
		if ( printer.setup(this) )
		{
			QPainter painter(&printer);
			QPixmap *CanvasPic = canvasView->canvas2Pixmap();
			painter.drawPixmap(0, 0, *CanvasPic);
		}
		return;
	}
	slotStatusBar(i18n("Printing aborted."),  IDS_STATUS);
}



bool MainWindow::queryClose()
{
	if ( editor->document()->isModified() )
	{
		slotStatusBar(i18n("Quitting KTurtle..."),  IDS_STATUS);
		// make sure the dialog looks good with new -- unnamed -- files.
		int result = KMessageBox::warningYesNoCancel( this,
			i18n("The program you are currently working on is not saved. "
			     "By quitting KTurtle you may lose the changes you have made."),
			i18n("Unsaved File"), i18n("&Save"), i18n("Discard Changes && &Quit") );
		if (result == KMessageBox::Cancel)
		{
			slotStatusBar(i18n("Quitting aborted."),  IDS_STATUS);
			return false;
		}
		else if (result == KMessageBox::Yes)
		{
			slotSaveFile();
			if ( CurrentFile.isEmpty() )
			{
				// when saveAs get cancelled or X-ed it should not quit
				slotStatusBar(i18n("Quitting aborted."),  IDS_STATUS);
				return false;
			}
		}
	}
	KConfig *config = kapp->config();
	config->setGroup("General Options");
	m_recentFiles->saveEntries(config, "Recent Files");
	config->sync();
	return true;
}

// END



// BEGIN run related functions

void MainWindow::slotExecute()
{
	if (b_fullscreen) // check if execution should be execution in 'fullscreen' mode
	{
		editorDock->hide();
		statusBar()->hide();
		menuBar()->hide();
		toolBar()->hide();
	}

	run->setEnabled(false); // set action indications
	pause->setEnabled(true);
	stop->setEnabled(true);

	// start paring
	slotStatusBar(i18n("Parsing commands..."),  IDS_STATUS);
	kdDebug(0)<<"############## PARSING STARTED ##############"<<endl;
	kapp->processEvents();
	errMsg = new ErrorMessage(this); // create an empty errorDialog
	QString txt = editorInterface->text() + "\x0a\x0a"; // parser expects input to have 2 delimiting newlines
	QTextIStream in(&txt); // create the stream
	Parser parser(in); // pass the reference to the stream to the parse object
	connect(&parser, SIGNAL( ErrorMsg(Token&, const QString&, uint) ),
	         errMsg, SLOT( slotAddError(Token&, const QString&, uint) ) );
	connect( errMsg, SIGNAL(setSelection(uint, uint, uint, uint) ),
	           this, SLOT(slotSetSelection(uint, uint, uint, uint) ) );
	parser.parse(); // and GO!
	TreeNode* root = parser.getTree(); // when finished parsing get the nodeTree
	kdDebug(0)<<"############## PARSING FINISHED ##############"<<endl;
	
	kdDebug(0)<<"TreeNode::showTree():"<<endl;
	root->showTree(root); // show nodeTree, this is a DEBUG OPTION (but nice)

	// start execution
	slotStatusBar(i18n("Executing commands..."),  IDS_STATUS);
	kdDebug(0)<<"############## EXECUTION STARTED ##############"<<endl;
	exe = new Executer(root); // make Executer object, 'exe', and pass it the nodeTree
	
	connect(this, SIGNAL( changeSpeed(int) ), exe, SLOT(slotChangeSpeed(int) ) );
	connect(this, SIGNAL( unpauseExecution() ), exe, SLOT( slotStopPausing() ) );
	connect( exe, SIGNAL( setSelection(uint, uint, uint, uint) ),
	        this, SLOT  ( slotSetSelection(uint, uint, uint, uint) ) );
	connect( exe, SIGNAL( ErrorMsg(Token&, const QString&, uint) ),
	      errMsg, SLOT  ( slotAddError(Token&, const QString&, uint) ) );
	connect( exe, SIGNAL( InputDialog(QString&) ), this, SLOT( slotInputDialog(QString&) ) );
	connect( exe, SIGNAL( MessageDialog(QString) ), this, SLOT( slotMessageDialog(QString) ) );

	// Connect the signals form Executer to the slots from Canvas:
	connect( exe, SIGNAL( Clear() ), canvasView, SLOT( slotClear() ) );
	connect( exe, SIGNAL( Go(double, double) ), canvasView, SLOT( slotGo(double, double) ) );
	connect( exe, SIGNAL( GoX(double) ), canvasView, SLOT( slotGoX(double) ) );
	connect( exe, SIGNAL( GoY(double) ), canvasView, SLOT( slotGoY(double) ) );
	connect( exe, SIGNAL( Forward(double) ), canvasView, SLOT( slotForward(double) ) );
	connect( exe, SIGNAL( Backward(double) ), canvasView, SLOT( slotBackward(double) ) );
	connect( exe, SIGNAL( Direction(double) ), canvasView, SLOT( slotDirection(double) ) );
	connect( exe, SIGNAL( TurnLeft(double) ), canvasView, SLOT( slotTurnLeft(double) ) );
	connect( exe, SIGNAL( TurnRight(double) ), canvasView, SLOT( slotTurnRight(double) ) );
	connect( exe, SIGNAL( Center() ), canvasView, SLOT( slotCenter() ) );
	connect( exe, SIGNAL( SetPenWidth(int) ), canvasView, SLOT( slotSetPenWidth(int) ) );
	connect( exe, SIGNAL( PenUp() ), canvasView, SLOT( slotPenUp() ) );
	connect( exe, SIGNAL( PenDown() ), canvasView, SLOT( slotPenDown() ) );
	connect( exe, SIGNAL( SetFgColor(int, int, int) ), canvasView, SLOT( slotSetFgColor(int, int, int) ) );
	connect( exe, SIGNAL( SetBgColor(int, int, int) ), canvasView, SLOT( slotSetBgColor(int, int, int) ) );
	connect( exe, SIGNAL( ResizeCanvas(int, int) ), canvasView, SLOT( slotResizeCanvas(int, int) ) );
	connect( exe, SIGNAL( SpriteShow() ), canvasView, SLOT( slotSpriteShow() ) );
	connect( exe, SIGNAL( SpriteHide() ), canvasView, SLOT( slotSpriteHide() ) );
	connect( exe, SIGNAL( SpritePress() ), canvasView, SLOT( slotSpritePress() ) );
	connect( exe, SIGNAL( SpriteChange(int) ), canvasView, SLOT( slotSpriteChange(int) ) );
	connect( exe, SIGNAL( Print(QString) ), canvasView, SLOT( slotPrint(QString) ) );
	connect( exe, SIGNAL( FontType(QString, QString) ), canvasView, SLOT( slotFontType(QString, QString) ) );
	connect( exe, SIGNAL( FontSize(int) ), canvasView, SLOT( slotFontSize(int) ) );
	connect( exe, SIGNAL( WrapOn() ), canvasView, SLOT( slotWrapOn() ) );
	connect( exe, SIGNAL( WrapOff() ), canvasView, SLOT( slotWrapOff() ) );
	connect( exe, SIGNAL( Reset() ), canvasView, SLOT( slotReset() ) );

	// START EXECUTION on the selected speed, and use the feedbacked boolean value
	slotChangeSpeed();
	if ( exe->run() ) slotStatusBar(i18n("Done."),  IDS_STATUS);
	else slotStatusBar(i18n("Execution aborted."),  IDS_STATUS);
	finishExecution();
	
	delete exe; // clean-up

	if ( errMsg->containsErrors() ) errMsg->display(); // if errors show them
}

void MainWindow::slotPauseExecution()
{
	if ( pause->isChecked() )
	{
		exe->pause();
		slotStatusBar(i18n("Execution paused."),  IDS_STATUS);
	}
	else
	{
		emit unpauseExecution();
		slotStatusBar(i18n("Executing commands..."),  IDS_STATUS);
	}
}

void MainWindow::slotAbortExecution()
{
	// To abort the executor, this can for instance be handy when the 
	// executer got stuck in a  user made endless loops...
	// to keep the program responding to interrupts like this, all the
	// loops have possible breaks. This activates them breaks:
	exe->abort();
}

void MainWindow::finishExecution()
{
	kdDebug(0)<<"############## EXECUTION FINISHED ##############"<<endl;
	executing = false;
	pause->setEnabled(false);
	pause->setChecked(false);
	run->setEnabled(true);
	stop->setEnabled(false);
	
	// if execution finished on FullSpeed the selection does not match the last executed command -> clear-it
	if (speed->currentItem() == 0) slotClearSelection();

	// if coming from fullscreen-mode show the editor, menu- and statusbar
	if (b_fullscreen) QTimer::singleShot( 1000, this, SLOT( slotFinishedFullScreenExecution() ) );
}

void MainWindow::slotChangeSpeed()
{
	emit changeSpeed( speed->currentItem() );
}


// slots for logo functions that need to use the MainWindow class:

void MainWindow::slotInputDialog(QString& value)
{
	value = KInputDialog::getText(i18n("Input"), value);
}

void MainWindow::slotMessageDialog(QString text)
{
	KMessageBox::information( this, text, i18n("Message") );
}

// END



// BEGIN editor connections (undo, redo, cut, copy, paste, cursor, selections, find, replace, linenumbers etc.)

void MainWindow::slotEditor()
{
	KAction *a = editor->actionCollection()->action("set_confdlg");
	a->activate();
}

void MainWindow::slotSetHighlightstyle(QString langCode)
{
	KTextEditor::HighlightingInterface *hli = dynamic_cast<KTextEditor::HighlightingInterface*>(doc);
	for (uint i = 0; i < hli->hlModeCount(); i++)
	{
		if (hli->hlModeName(i) == langCode)
		{
			hli->setHlMode(i);
			return;
		}
	}
	// and the fallback:
	for (uint i = 0; i < hli->hlModeCount(); i++)
	{
		if(hli->hlModeName(i) == "en_US") hli->setHlMode(i);
	}
}


void MainWindow::slotUndo()
{
	dynamic_cast<KTextEditor::UndoInterface*>(doc)->undo();
}

void MainWindow::slotRedo()
{
	dynamic_cast<KTextEditor::UndoInterface*>(doc)->redo();
}


void MainWindow::slotCut()
{
	dynamic_cast<KTextEditor::ClipboardInterface*>(editor)->cut();
}

void MainWindow::slotCopy()
{
	dynamic_cast<KTextEditor::ClipboardInterface*>(editor)->copy();
}

void MainWindow::slotPaste()
{
	dynamic_cast<KTextEditor::ClipboardInterface*>(editor)->paste();
}


void MainWindow::slotSelectAll()
{
	dynamic_cast<KTextEditor::SelectionInterface*>(doc)->selectAll();
}

void MainWindow::slotClearSelection()
{
	dynamic_cast<KTextEditor::SelectionInterface*>(doc)->clearSelection();
}

void MainWindow::slotFind()
{
	KAction *a = editor->actionCollection()->action("edit_find");
	a->activate();
}

void MainWindow::slotFindNext()
{
	KAction *a = editor->actionCollection()->action("edit_find_next");
	a->activate();
}

void MainWindow::slotFindPrevious()
{
	KAction *a = editor->actionCollection()->action("edit_find_prev");
	a->activate();
}

void MainWindow::slotReplace()
{
	KAction* a = editor->actionCollection()->action("edit_replace");
	a->activate();
}

void MainWindow::slotToggleInsert()
{
	KToggleAction *a = dynamic_cast<KToggleAction*>(editor->actionCollection()->action("set_insert"));
	a->activate();
	if (a) statusBar()->changeItem(a->isChecked() ? i18n(" OVR ") : i18n(" INS "), IDS_INS);
}

void MainWindow::slotIndent()
{
	KAction *a = editor->actionCollection()->action("tools_indent");
	a->activate();
}

void MainWindow::slotUnIndent()
{
	KAction *a = editor->actionCollection()->action("tools_unindent");
	a->activate();
}

void MainWindow::slotCleanIndent()
{
	KAction *a = editor->actionCollection()->action("tools_cleanIndent");
	a->activate();
}

void MainWindow::slotComment()
{
	KAction *a = editor->actionCollection()->action("tools_comment");
	a->activate();
}

void MainWindow::slotUnComment()
{
	KAction *a = editor->actionCollection()->action("tools_uncomment");
	a->activate();
}

void MainWindow::slotToggleLineNumbers()
{
	KToggleAction *a = dynamic_cast<KToggleAction*>( editor->actionCollection()->action("view_line_numbers") );
	a->activate();
}

void MainWindow::slotInsertText(QString str)
{
	uint StartLine, StartCol, EndLine, EndCol;
	dynamic_cast<KTextEditor::ViewCursorInterface*>(editor)->cursorPositionReal(&StartLine, &StartCol);
	dynamic_cast<KTextEditor::EditInterface*>(doc)->insertText(StartLine, StartCol, str);
	dynamic_cast<KTextEditor::ViewCursorInterface*>(editor)->cursorPositionReal(&EndLine, &EndCol);
	dynamic_cast<KTextEditor::SelectionInterface*>(doc)->setSelection(StartLine, StartCol, EndLine, EndCol);
}

void MainWindow::slotSetCursorPos(uint row, uint col)
{
	dynamic_cast<KTextEditor::ViewCursorInterface*>(editor)->setCursorPositionReal(row - 1, col);
	// kdDebug(0)<<"Cursor set to: ("<<row-1<<", "<<col<<")"<<endl;
}

void MainWindow::slotSetSelection(uint StartLine, uint StartCol, uint EndLine, uint EndCol)
{
	dynamic_cast<KTextEditor::SelectionInterface*>(doc)->setSelection(StartLine - 1, StartCol - 1, EndLine - 1, EndCol - 1);
	// kdDebug(0)<<"Selection set to: ("<<StartLine<<", "<<StartCol<<", "<<EndLine<<", "<<EndCol<<")"<<endl;
}
// END



// BEGIN fullscreen functions

void MainWindow::slotToggleFullscreen()
{
	if (!b_fullscreen) showFullScreen(); // both calls will generate event triggering updateFullScreen()
	else if ( isFullScreen() ) showNormal();
}

bool MainWindow::event(QEvent* e)
{
	// executes updateFullScreen() after a ShowFullScreen or ShowNormal event got triggered
	if (e->type() == QEvent::ShowFullScreen || e->type() == QEvent::ShowNormal) updateFullScreen();
	return KMainWindow::event(e);
}

void MainWindow::updateFullScreen()
{
	if (isFullScreen() == b_fullscreen) return;
	b_fullscreen = isFullScreen();
	if (m_fullscreen) m_fullscreen->setChecked(b_fullscreen);
}

void MainWindow::slotFinishedFullScreenExecution()
{
	restartOrBackDialog = new RestartOrBack(this); // we have to make some to delete some
	if ( errMsg->containsErrors() ) slotBackToFullScreen(); // straight back to edit if there where errors 
	else
	{
		connect( restartOrBackDialog, SIGNAL( user1Clicked() ), this, SLOT( slotRestartFullScreen() ) );
		connect( restartOrBackDialog, SIGNAL( user2Clicked() ), this, SLOT( slotBackToFullScreen() ) );
		connect( restartOrBackDialog, SIGNAL( finished() ), this, SLOT( slotBackToFullScreen() ) );
		restartOrBackDialog->show();
		restartOrBackDialog->move(50, 50);
	}
}

void MainWindow::slotBackToFullScreen()
{
	delete restartOrBackDialog;
	editorDock->show();
	statusBar()->show();
	menuBar()->show();
	toolBar()->show();
}

void MainWindow::slotRestartFullScreen()
{
	delete restartOrBackDialog;
	slotExecute();
}

// END



// BEGIN configuration related functions

void MainWindow::slotSettings()
{
	// Check if there is already a dialog, if so bring it to the foreground.
	if ( KConfigDialog::showDialog("settings") ) return;

	// Create a new dialog with the same name as the above checking code.
	KConfigDialog *dialog = new KConfigDialog(this, "settings", Settings::self() );
	// connect the help
	connect( dialog, SIGNAL( helpClicked() ), this, SLOT( slotSettingsHelp() ) );
	
	// making the filling for the 'General' settings dept.
	general = new QWidget();
	QGridLayout *generalLayout = new QGridLayout( general, 1, 1, 11, 6, "generalLayout");
	WidthHeightBox = new QGroupBox( i18n("Initial Canvas Size"), general );
	WidthHeightBox->setColumnLayout(0, Qt::Vertical );
	WidthHeightBox->layout()->setSpacing( 6 );
	WidthHeightBox->layout()->setMargin( 11 );
	QVBoxLayout *WidthHeightBoxLayout = new QVBoxLayout( WidthHeightBox->layout() );
	WidthHeightBoxLayout->setAlignment( Qt::AlignTop );
	QHBoxLayout *layout3 = new QHBoxLayout( 0, 0, 6, "layout3");
	QVBoxLayout *layout2 = new QVBoxLayout( 0, 0, 6, "layout2");
	
	QVBoxLayout *layout1 = new QVBoxLayout( 0, 0, 6, "layout1");

	kcfg_CanvasWidth = new KIntNumInput( WidthHeightBox, "kcfg_CanvasWidth" );
	kcfg_CanvasWidth->setValue( 400 );
	kcfg_CanvasWidth->setMinValue( 1 );
	kcfg_CanvasWidth->setReferencePoint( 1 );
	layout1->addWidget( kcfg_CanvasWidth );

	kcfg_CanvasHeight = new KIntNumInput( WidthHeightBox, "kcfg_CanvasHeight" );
	kcfg_CanvasHeight->setValue( 300 );
	kcfg_CanvasHeight->setMinValue( 1 );
	kcfg_CanvasHeight->setReferencePoint( 1 );
	layout1->addWidget( kcfg_CanvasHeight );

	WidthLabel = new QLabel( kcfg_CanvasWidth, i18n("Canvas &width:"), WidthHeightBox );
	layout2->addWidget( WidthLabel );
	HeightLabel = new QLabel( kcfg_CanvasHeight, i18n("Ca&nvas height:"), WidthHeightBox );
	layout2->addWidget( HeightLabel );
	layout3->addLayout( layout2 );

	layout3->addLayout( layout1 );
	WidthHeightBoxLayout->addLayout( layout3 );
	QLabel* WidthHeightLabel = new QLabel(i18n("You need to restart before these settings have effect"), WidthHeightBox);
	WidthHeightBoxLayout->addWidget( WidthHeightLabel );
	generalLayout->addWidget( WidthHeightBox, 0, 0 );
	general->resize( QSize(234, 109).expandedTo(minimumSizeHint()) );

	dialog->addPage( general, i18n("General"), "package_settings", i18n("General Settings") );

	// making the filling for the 'Language' settings dept.
	QWidget *language = new QWidget();
	QGridLayout *languageLayout = new QGridLayout( language, 1, 1, 11, 6, "Form1Layout");
	QGroupBox *groupBox1 = new QGroupBox( language, "groupBox1" );
	groupBox1->setColumnLayout(0, Qt::Vertical );
	groupBox1->layout()->setSpacing( 6 );
	groupBox1->layout()->setMargin( 11 );
	QGridLayout *groupBox1Layout = new QGridLayout( groupBox1->layout() );
	groupBox1Layout->setAlignment( Qt::AlignTop );

	QVBoxLayout *layout4 = new QVBoxLayout( 0, 0, 6, "layout4");

	kcfg_LanguageComboBox = new KComboBox(groupBox1, "kcfg_LanguageComboBox");
	kcfg_LanguageComboBox->setEditable(false);
	QStringList LogoLanguageList = Settings::logoLanguageList();
	// Add the full language names to the items
	for ( QStringList::Iterator it = LogoLanguageList.begin(); it != LogoLanguageList.end(); ++it ) {
		*it = KGlobal::locale()->twoAlphaToLanguageName( (*it).left(2) ) + " (" + *it + ")";
	}
	kcfg_LanguageComboBox->insertStringList(LogoLanguageList);
	
	LanguageLabel = new QLabel(kcfg_LanguageComboBox, i18n("&Select the language for the Logo commands:"), groupBox1);
	layout4->addWidget( LanguageLabel );
	layout4->addWidget( kcfg_LanguageComboBox );
	LanguageLabel->setBuddy( kcfg_LanguageComboBox );

	groupBox1Layout->addLayout( layout4, 0, 0 );
	languageLayout->addWidget( groupBox1, 0, 0 );
	language->resize( QSize(373, 80).expandedTo(minimumSizeHint()) );

	dialog->addPage( language, i18n("Language"), "locale", i18n("Language Settings") );

	// When the user clicks OK or Apply we want to update our settings.
	connect( dialog, SIGNAL( settingsChanged() ), this, SLOT( slotUpdateSettings() ) );

	// Display the dialog is there where errors.
	dialog->setInitialSize( QSize(550, 300) );
	dialog->show();
}

void MainWindow::slotUpdateSettings()
{
	// get the selected language as a language code
	QString selectedLogoLanguage = kcfg_LanguageComboBox->currentText().section( "(", -1, -1 ).remove(")");
	// update the settings
	Settings::setLogoLanguage( selectedLogoLanguage );
	Settings::setLanguageComboBox( kcfg_LanguageComboBox->currentItem() );
	Settings::writeConfig();
	// set the HLstyle
	slotSetHighlightstyle( selectedLogoLanguage );
	// set the statusbar to display the language as just updated
	// TODO maybe this language name can be more pretty by not using ".left(2)", ie "American English" would than be possible... [if this is possible this should be fixed at more places.]
	KConfig entry(locate("locale", "all_languages"));
	entry.setGroup(Settings::logoLanguage().left(2));
	slotStatusBar(i18n("Command language: %1").arg( entry.readEntry("Name") ), IDS_LANG);
	
	delete translate; // to update the currently used language
	translate = new Translate();
}

void MainWindow::readConfig(KConfig *config)
{
	config->setGroup("General Options");
	m_recentFiles->loadEntries(config, "Recent Files");
	KConfig entry(locate("locale", "all_languages"));
	entry.setGroup(Settings::logoLanguage().left(2));
	slotStatusBar(i18n("Command language: %1").arg( entry.readEntry("Name") ), IDS_LANG);
}

void MainWindow::slotSettingsHelp()
{
	kapp->invokeHelp("settings-configure", "", "");
}

// END



// BEGIN help related functions

void MainWindow::slotContextHelp()
{
// somehow the 'anchor' parameter of invokeHelp is not working correcctly (yet)

// this is/was appearently a bug in KHTML that Waldo Bastian kindly and quikly fixed.

// Ooh... and we also want a DCOPmethod to close the sidebar since it over-informs...
// Ooh3... we want fancy help (using KHTML) @ errormessage dialog
// Ooh4... And we might also have to keep track of the KHelpCenter instance we open so
// we will not end up with loads of them
// 

// IDEA!!:  put all the keyword in a i18n("...") this will make translating them a lot easier!!!
// MAYBE THIS IS ALSO POSSIBLE FOR THE INTERPRETER!!!!
// this should be discussed with translators (and please think of the highlight-themes too (since
// right now thay can probably be translated with a simple perl script 

	kdDebug(0)<<"help requested on this text: "<<helpKeyword<<endl;
	
	QString helpWord;
	if ( helpKeyword == i18n("<no keyword>") )
	{
		KMessageBox::information( this, i18n("There is currently no text under the cursor to get help on."), i18n("Nothing Under Cursor") );
		return;
	}
	else if ( helpKeyword == i18n("<number>") )     helpWord = "number";
	else if ( helpKeyword == i18n("<string>") )     helpWord = "string";
	else if ( helpKeyword == i18n("<assignment>") ) helpWord = "assignment";
	else if ( helpKeyword == i18n("<question>") )   helpWord = "questions";
	else if ( helpKeyword == i18n("<name>") )       helpWord = "name";
	else if ( helpKeyword == i18n("<comment>") )    helpWord = "comment";
	else
	{
		// make lowercase
		helpWord = helpKeyword.lower();
		// if the key is an alias translate that alias to a key
		if      ( !translate->alias2key(helpKeyword).isEmpty() ) helpWord = translate->alias2key(helpKeyword);
		else if ( !translate->name2key (helpKeyword).isEmpty() ) helpWord = translate->name2key (helpKeyword);
		
		// at this point helpKeyword should contain a valid
		// section/anchor-id that can be used found in the doc
		// if not... who cares :)... well let's put an debugMsg for that occasion:
		else kdDebug(0)<<"Error in MainWindow::slotContextHelp: could not translate \""<<helpKeyword<<"\""<<endl;
	}
	
	kdDebug(0)<<"trying to open a help page using this keyword: "<<helpWord<<endl;
	
	kapp->invokeHelp(helpWord, "", "");
	
	QString help2statusBar;
	if ( helpKeyword.startsWith("<") ) help2statusBar = helpKeyword;
	else                               help2statusBar = i18n("\"%1\"").arg(helpKeyword);
	slotStatusBar(i18n("Displaying help on %1").arg(help2statusBar), IDS_STATUS);
}

void MainWindow::slotContextHelpUpdate()
{
	uint row, col;
	dynamic_cast<KTextEditor::ViewCursorInterface*>(editor)->cursorPositionReal(&row, &col);
	QString line = dynamic_cast<KTextEditor::EditInterface*>(doc)->textLine(row);
	
	// two shortcuts so we dont do all the CPU intensive regexp stuff when it not needed
	if ( line.stripWhiteSpace().startsWith("#") )
	{
		helpKeyword = i18n("<comment>");
		ContextHelp->setText( i18n("Help on: %1").arg(helpKeyword) );
		return;
	}
	if ( line.stripWhiteSpace().isEmpty() || line.mid(col-1,2).stripWhiteSpace().isEmpty() )
	{
		helpKeyword = i18n("<no keyword>");
		ContextHelp->setText( i18n("Help on: %1").arg(helpKeyword) );
		return;
	}
	
	int start, length, pos;
	
	pos = 0;
	if ( line.contains('"') )
	{
		QRegExp delimitedStrings("(\"[^\"\\\\\\r\\n]*(\\\\.[^\"\\\\\\r\\n]*)*\")");
		while ( delimitedStrings.search(line, pos) != -1 )
		{
			// kdDebug(0)<<"stringsearch: >>"<<pos<<"<<"<<endl;
			start  = delimitedStrings.search(line, pos);
			length = delimitedStrings.matchedLength(); // the length of the last matched string, or -1 if there was no match
			if ( col >= (uint)start && col < (uint)(start+length) )
			{
				helpKeyword = i18n("<string>");
				ContextHelp->setText( i18n("Help on: %1").arg(helpKeyword) );
				return;
			}
			pos += (length <= 0 ? 1 : length);
		}
	}
	
	
	// except for "strings" this regexp effectively separates logo code in 'words' (in a very broad meaning)
	QRegExp splitter("(([^ ,+\\-*/()=<>[!]|(?!==|<=|>=|!=))*)|(([ ,+\\-*/()=<>[!]|==|<=|>=|!=))");
	
	pos = 0;
	while (splitter.search(line, pos) != -1)
	{
		start  = splitter.search(line, pos);
		length = splitter.matchedLength();
		if ( col <  (uint)start ) break;
		if ( col >= (uint)start && col < (uint)(start+length) )
		{
			QString cursorWord = line.mid( (uint)start, (uint)length );
			// kdDebug(0)<<"splitsearch: >>"<<cursorWord<<"<<"<<endl;
			
			if      ( cursorWord.stripWhiteSpace().isEmpty() ) helpKeyword = i18n("<no keyword>");

			else if ( !translate->name2key(  cursorWord.lower() ).isEmpty() ||
			          !translate->alias2key( cursorWord.lower() ).isEmpty() ) helpKeyword = cursorWord;

			else if ( cursorWord.find( QRegExp("[\\d.]+") ) == 0 ) helpKeyword = i18n("<number>");

			else if ( cursorWord.find( QRegExp("[+\\-*/\\(\\)]") ) == 0 ) helpKeyword = i18n("<math>");

			else if ( cursorWord == QString("=") ) helpKeyword = i18n("<assignment>");

			else if ( cursorWord.find( QRegExp("==|<|>|<=|>=|!=") ) == 0 ) helpKeyword = i18n("<question>");

			// if we come here we either have an ID of some sort or an error
			// all we can do is try to catch some errors (TODO) and then...
			
			else helpKeyword = i18n("<name>");
			
			ContextHelp->setText( i18n("Help on: %1").arg(helpKeyword) );
			return;
		}
		pos += (length <= 0 ? 1 : length); // the pos had to be increased with at least one
	}
	
	// we allready cached some in the beginning of this method; yet its still needed as fall-through
	helpKeyword = i18n("<no keyword>");
	ContextHelp->setText( i18n("Help on: %1").arg(helpKeyword) );
}

// END



// BEGIN misc. functions

void MainWindow::slotColorPicker()
{
	// in the constructor picker is initialised as 0
	// if picker is 0 when this funktion is called a colorpickerdialog is created and connected
	if (picker == 0)
	{
		picker = new ColorPicker(this);
		if(picker == 0) return; // safety
		connect( picker, SIGNAL( visible(bool) ), colorpicker, SLOT( setChecked(bool) ) );
		connect( picker, SIGNAL( ColorCode(QString) ), this, SLOT( slotInsertText(QString) ) );
	}
	// if picker is not 0, there is a colorpickerdialog which only needs to be shown OR hidden
	if ( picker->isHidden() )
	{
		picker->show();
		colorpicker->setChecked(true);
	}
	else
	{
		picker->hide();
		colorpicker->setChecked(false);
	}
}

void MainWindow::slotUpdateCanvas()
{
	// fixes a non updateing bug
	// I tried doing this by connecting Canvas's resized to baseWidget's update...
	// but i had no luck :(    ...    this worked though :)
	canvasView->hide();
	canvasView->show();
}

// END


#include "kturtle.moc"
