1 //
2 //   This file is part of the source of CoCoALib, the CoCoA Library.
3 //
4 //   CoCoALib is free software: you can redistribute it and/or modify
5 //   it under the terms of the GNU General Public License as published by
6 //   the Free Software Foundation, either version 3 of the License, or
7 //   (at your option) any later version.
8 //
9 //   CoCoALib is distributed in the hope that it will be useful,
10 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //   GNU General Public License for more details.
13 //
14 //   You should have received a copy of the GNU General Public License
15 //   along with CoCoALib.  If not, see <http://www.gnu.org/licenses/>.
16 
17 #ifdef C5IDE
18 
19 #include <QFileDialog>
20 #include <QFileInfo>
21 #include <QList>
22 #include <QPlastiqueStyle>
23 #include <QFontDialog>
24 #include <QCoreApplication>
25 #include <QSettings>
26 
27 #include "Banner.H"
28 #include "C5.H"
29 #include "C5Utils.H"
30 #include "CoCoA/GlobalManager.H"
31 #include "CoCoA/interrupt.H"
32 #include "qce-config.h"
33 #include "qeditor.h"
34 #include "qlanguagefactory.h"
35 #include "qlinemarksinfocenter.h"
36 #include "qeditorinputbinding.h"
37 #include "widgets/qgotolinepanel.h"
38 #include "widgets/qfoldpanel.h"
39 #include "widgets/qstatuspanel.h"
40 #include "widgets/qlinemarkpanel.h"
41 #include "widgets/qsearchreplacepanel.h"
42 #include "widgets/qlinenumberpanel.h"
43 #include "widgets/qlinechangepanel.h"
44 
45 using namespace std;
46 using namespace boost;
47 using namespace CoCoA;
48 using namespace CoCoA::LexerNS;
49 using namespace CoCoA::AST;
50 using namespace CoCoA::InterpreterNS;
51 
initQCodeEdit()52 void initQCodeEdit() {
53 	// the following are needed to statically (and successfully ;-) ) link QCodeEdit
54 	// note: this function must be outside any namespace (because of the macro Q_INIT_RESOURCE)
55 	QGotoLinePanel::_register();
56 	QFoldPanel::_register();
57 	QStatusPanel::_register();
58 	QLineMarkPanel::_register();
59 	QSearchReplacePanel::_register();
60 	QLineNumberPanel::_register();
61 	QLineChangePanel::_register();
62 	Q_INIT_RESOURCE(Edyuk);
63 }
64 
65 namespace {
66 	const QString SETTING_FONT_FAMILY("font/family");
67 	const QString SETTING_FONT_POINTSIZE("font/pointSize");
68 	const QString SETTING_FONT_WEIGHT("font/weight");
69 	const QString SETTING_FONT_ITALIC("font/italic");
70 	const QString SETTING_COLORSCHEME_NAME("colorScheme/name");
71 	const QString SETTING_EMACS_LIKE_KEYBINDINGS("emacs/keyBindings");
72 }
73 
74 namespace CoCoA {
75 
76 namespace AST {
eval(RuntimeEnvironment * re)77 	intrusive_ptr<Value> Expression::eval(RuntimeEnvironment *re) const {
78 		Interpreter * const interpreter = re->interpreter;
79 		bool doStepOver = false;
80 		if (interpreter->singleStepExecution && !CoCoA::GetAndResetSignalReceived() && !this->skipDebugging())
81 			doStepOver = interpreter->pause(this);
82 		intrusive_ptr<Value> result;
83 		try	{
84 			result = this->implEval(re);
85 			if (doStepOver && interpreter->doStepOver)
86 				interpreter->singleStepExecution = true;
87 		} catch (InterruptException &) {
88 			throw;
89 		} catch (RuntimeException &) {
90 			if (doStepOver && interpreter->doStepOver)
91 				interpreter->singleStepExecution = true;
92 			throw;
93 		}
94 		return result;
95 	}
96 
skipDebugging()97 	bool ParsedObject::skipDebugging() const {
98 		return false;
99 	}
100 
skipDebugging()101 	bool EvalStatement::skipDebugging() const {
102 			return true;
103 	}
104 
skipDebugging()105 	bool Statements::skipDebugging() const {
106 			return true;
107 	}
108 
skipDebugging()109 	bool EmptyStatement::skipDebugging() const {
110 			return true;
111 	}
112 
skipDebugging()113 	bool InvocationExpression::skipDebugging() const {
114 			return this->targetExp->skipDebugging();
115 	}
116 
execute(RuntimeEnvironment * re)117 	void Statement::execute(RuntimeEnvironment *re) {
118 		Interpreter * const interpreter = re->interpreter;
119 		bool doStepOver = false;
120 		if (interpreter->singleStepExecution && !CoCoA::GetAndResetSignalReceived() && !this->skipDebugging()) {
121 			doStepOver = interpreter->pause(this);
122 			if (doStepOver) {
123 				if (dynamic_cast<BreakStatement *>(this) || dynamic_cast<ContinueStatement *>(this))
124 					re->getCurrentFrame()->singleStepOnPop = true;
125 				if (dynamic_cast<ReturnStatement *>(this))
126 					re->getCurrentFunctionFrame()->singleStepOnPop = true;
127 			}
128 		}
129 		try	{
130 			this->implExecute(re);
131 			if (doStepOver && interpreter->doStepOver)
132 				interpreter->singleStepExecution = true;
133 		} catch (InterruptException &) {
134 			throw;
135 		} catch (RuntimeException &) {
136 			if (doStepOver && interpreter->doStepOver)
137 				interpreter->singleStepExecution = true;
138 			throw;
139 		}
140 	}
141 } // namespace AST
142 
143 namespace {
144 	const int COL_NAME = 0;
145 	const int COL_KIND = 1;
146 	const int COL_VALUE = 2;
147 }
148 
149 namespace InterpreterNS {
pause(intrusive_ptr<const ParsedObject> po)150 	bool Interpreter::pause(intrusive_ptr<const ParsedObject> po) {
151 		assert(this_thread::get_id()!=IDE::Console::guiThreadId);
152 		const Frame *tl = this->runtimeEnvironment.getTopLevelFrame();
153 		for(Frame *f=this->runtimeEnvironment.getCurrentFrame(); f>=tl;)
154 			(f--)->singleStepOnPop = false;
155 		this->doStepOver = false;
156 		this->pausedOn = po;
157 		InterpreterStatus status = this->status;
158 		assert(status==IS_RUNNING || status==IS_RUNNING_BUILTIN);
159 		this->status = IS_PAUSED;
160 		unique_lock<mutex> lock(this->mut);
161 		this->mustResume = false;
162 		do
163 			this->condVar.wait(lock);
164 		while (!this->mustResume);
165 		this->pausedOn = 0;
166 		this->status = status;
167 		this->runtimeEnvironment.getOutputStream()->flush(); // this is used to sync (that is, wait until the GUI notices the status has changed)
168 		this->checkForInterrupts(po);
169 		return this->doStepOver;
170 	}
171 
resume()172 	void Interpreter::resume() {
173 		assert(this_thread::get_id()==IDE::Console::guiThreadId);
174 		assert(this->status==IS_PAUSED);
175 		this->mustResume = true;
176 		this->condVar.notify_one();
177 	}
178 
initTreeWidget(QTreeWidgetItem * item)179 	void RightValue::initTreeWidget(QTreeWidgetItem *item) const {
180 		ostringstream os;
181 		os << this;
182 		item->setText(COL_VALUE, QString::fromStdString(os.str()));
183 	}
184 
initTreeWidget(QTreeWidgetItem * item)185 	void VoidValue::initTreeWidget(QTreeWidgetItem *item) const {
186 		item->setText(COL_VALUE, "Uninitialized");
187 	}
188 
initTreeWidget(QTreeWidgetItem * item)189 	void BuiltInFunction::initTreeWidget(QTreeWidgetItem *item) const {
190 		item->setText(COL_VALUE, "A built-in fn-proc");
191 	}
192 
initTreeWidget(QTreeWidgetItem * item)193 	void UserDefinedFunction::initTreeWidget(QTreeWidgetItem *item) const {
194 		item->setText(COL_VALUE, "A user-defined fn-proc");
195 	}
196 
initTreeWidget(QTreeWidgetItem * item)197 	void TYPE::initTreeWidget(QTreeWidgetItem *item) const {
198 		item->setText(COL_VALUE, "A type");
199 	}
200 
initTreeWidget(QTreeWidgetItem * item)201 	void OSTREAM::initTreeWidget(QTreeWidgetItem *item) const {
202 		item->setText(COL_VALUE, "An output-stream");
203 	}
204 
initTreeWidget(QTreeWidgetItem * item)205 	void LIST::initTreeWidget(QTreeWidgetItem *item) const {
206 		const ContainerType::size_type size = this->size();
207 		item->setText(COL_VALUE, QString::fromStdString("A "+lexical_cast<string>(size)+"-element list"));
208 		for(ContainerType::size_type a=0; a<size; ++a) {
209 			QTreeWidgetItem * const child = new QTreeWidgetItem();
210 			child->setText(COL_NAME, QString::fromStdString("["+lexical_cast<string>(a+1)+"]"));
211 			item->addChild(child);
212 			container[a]->initTreeWidget(child);
213 		}
214 	}
215 
initTreeWidget(QTreeWidgetItem * item)216 	void TaggedValue::initTreeWidget(QTreeWidgetItem *item) const {
217 		item->setText(COL_VALUE, QString::fromStdString("A tagged value"));
218 		QTreeWidgetItem * const child1 = new QTreeWidgetItem();
219 		child1->setText(COL_NAME, QString::fromStdString("Tag"));
220 		child1->setText(COL_VALUE, QString::fromStdString(this->tag));
221 		item->addChild(child1);
222 		QTreeWidgetItem * const child2 = new QTreeWidgetItem();
223 		child2->setText(COL_NAME, QString::fromStdString("Value"));
224 		item->addChild(child2);
225 		this->value->initTreeWidget(child2);
226 	}
227 
initTreeWidget(QTreeWidgetItem * item)228 	void RECORD::initTreeWidget(QTreeWidgetItem *item) const {
229 		const MapType::size_type size = this->numberOfFields();
230 		item->setText(COL_VALUE, QString::fromStdString("A "+lexical_cast<string>(size)+"-field record"));
231 		for(MapType::const_iterator pos = this->fields.begin(); pos!=this->fields.end(); ++pos) {
232 			QTreeWidgetItem * const child = new QTreeWidgetItem();
233 			child->setText(COL_NAME, QString::fromStdString("."+pos->first));
234 			item->addChild(child);
235 			pos->second->initTreeWidget(child);
236 		}
237 	}
238 
initTreeWidget(QTreeWidgetItem * item)239 	void IntMapValue::initTreeWidget(QTreeWidgetItem *item) const {
240 		item->setText(COL_VALUE, QString::fromStdString("A map"));
241 		for(MapType::const_iterator pos = this->map.begin(); pos!=this->map.end(); ++pos) {
242 			QTreeWidgetItem * const child = new QTreeWidgetItem();
243 			child->setText(COL_NAME, QString::fromStdString("["+lexical_cast<string>(pos->first)+"]"));
244 			item->addChild(child);
245 			pos->second->initTreeWidget(child);
246 		}
247 	}
248 } // namespace InterpreterNS
249 
250 namespace IDE {
251 
252 thread::id Console::guiThreadId;
253 
highlightBlock(const QString & text)254 void CocoaHighlighter::highlightBlock(const QString &text) {
255 	assert(this_thread::get_id()==Console::guiThreadId);
256 	if (!this->alwaysEnabled) {
257 		QTextBlockUserData *data = this->currentBlockUserData();
258 		if (data) {
259 			assert(dynamic_cast<HighlightingInfo *>(data));
260 			if (!static_cast<HighlightingInfo *>(data)->doHighlight)
261 				return;
262 		} else {
263 			this->setCurrentBlockUserData(new HighlightingInfo(this->enabled));
264 			if (!this->enabled)
265 				return;
266 		}
267 	}
268 	this->unclosedComment = this->unclosedStringLiteral = false;
269 	const int textLenght = text.length();
270 	setCurrentBlockState(0);
271 	int index=0;
272 	const int previous = previousBlockState();
273 	if (previous==IN_SINGLEQUOTE_STRING) {
274 		index = -1;
275 		goto findSingleQuoteClosing;
276 	}
277 	if (previous==IN_DOUBLEQUOTE_STRING) {
278 		index = -1;
279 		goto findDoubleQuoteClosing;
280 	}
281 	if (previous==IN_TRIPLEQUOTE_STRING) {
282 		index = -3;
283 		goto findTripleQuoteClosing;
284 	}
285 	if (previous==IN_COMMENT) {
286 		index = -2;
287 		goto findCommentClosing;
288 	}
289 	while(index<textLenght) {
290 		if (text.mid(index,2)==tr("--") || text.mid(index,2)==tr("//")) {
291 			int endPos = text.indexOf(this->singlelineCommentEndRE, index+2);
292 			int len;
293 			if (endPos==-1) {
294 				if (index<=0)
295 					index = 0;
296 				len = textLenght-index;
297 			} else {
298 				len = this->singlelineCommentEndRE.matchedLength();
299 				if (index>=0)
300 					len += 2;
301 				else
302 					index = 0;
303 			}
304 			setFormat(index, len, this->commentFormat);
305 			index += len;
306 			continue;
307 		}
308 		if (text.mid(index,2)==tr("/*")) {
309 findCommentClosing:
310 			setCurrentBlockState(IN_COMMENT);
311 			int endPos = text.indexOf(this->commentEndRE, index+2);
312 			int len;
313 			if (endPos==-1) {
314 				this->unclosedComment = true;
315 				if (index<=0)
316 					index = 0;
317 				len = textLenght-index;
318 			} else {
319 				len = this->commentEndRE.matchedLength();
320 				if (index>=0)
321 					len += 2;
322 				else
323 					index = 0;
324 				setCurrentBlockState(NONE);
325 			}
326 			setFormat(index, len, this->commentFormat);
327 			index += len;
328 			continue;
329 		}
330 		if (text.mid(index, 3)==tr("\"\"\"")) {
331 findTripleQuoteClosing:
332 			setCurrentBlockState(IN_TRIPLEQUOTE_STRING);
333 			int endPos = text.indexOf(this->tripleQuoteliteralStringEndRE, index+3);
334 			int len;
335 			if (endPos==-1) {
336 				this->unclosedStringLiteral = true;
337 				if (index<=0)
338 					index = 0;
339 				len = textLenght-index;
340 			} else {
341 				len = this->tripleQuoteliteralStringEndRE.matchedLength();
342 				if (index>=0)
343 					len += 3;
344 				else
345 					index = 0;
346 				setCurrentBlockState(NONE);
347 			}
348 			setFormat(index, len, this->literalStringFormat);
349 			index += len;
350 			continue;
351 		}
352 		if (text.mid(index, 1)==tr("\'")) {
353 findSingleQuoteClosing:
354 			setCurrentBlockState(IN_SINGLEQUOTE_STRING);
355 			int endPos = text.indexOf(this->singleQuoteliteralStringEndRE, index+1);
356 			int len;
357 			if (endPos==-1) {
358 				this->unclosedStringLiteral = true;
359 				if (index<=0)
360 					index = 0;
361 				len = textLenght-index;
362 			} else {
363 				len = this->singleQuoteliteralStringEndRE.matchedLength();
364 				if (index>=0)
365 					++len;
366 				else
367 					index = 0;
368 				setCurrentBlockState(NONE);
369 			}
370 			setFormat(index, len, this->literalStringFormat);
371 			index += len;
372 			continue;
373 		}
374 		if (text.mid(index, 1)==tr("\"")) {
375 findDoubleQuoteClosing:
376 			setCurrentBlockState(IN_DOUBLEQUOTE_STRING);
377 			int endPos = text.indexOf(this->doubleQuoteliteralStringEndRE, index+1);
378 			int len;
379 			if (endPos==-1) {
380 				this->unclosedStringLiteral = true;
381 				if (index<=0)
382 					index = 0;
383 				len = textLenght-index;
384 			} else {
385 				len = this->doubleQuoteliteralStringEndRE.matchedLength();
386 				if (index>=0)
387 					++len;
388 				else
389 					index = 0;
390 				setCurrentBlockState(NONE);
391 			}
392 			setFormat(index, len, this->literalStringFormat);
393 			index += len;
394 			continue;
395 		}
396 		const char firstChar = text.mid(index, 1).toStdString()[0];
397 		if (IsBlank(firstChar) && text.indexOf(this->blanksRE, index)==index) {
398 			index += this->blanksRE.matchedLength();
399 			continue;
400 		}
401 		if (firstChar=='_' || firstChar=='$')
402 			goto try_identifier;
403 		if (firstChar=='?')
404 			goto try_keyword;
405 		if (isalpha(firstChar)) {
406 			if (text.indexOf(this->typeRE, index)==index) {
407 				const int len = this->typeRE.matchedLength();
408 				setFormat(index, len, this->typeFormat);
409 				index += len;
410 				continue;
411 			}
412 			if (text.indexOf(this->constantRE, index)==index) {
413 				const int len = this->constantRE.matchedLength();
414 				setFormat(index, len, this->constantFormat);
415 				index += len;
416 				continue;
417 			}
418 try_keyword: if (text.indexOf(this->keywordRE, index)==index) { // note: RECORD is not a keyword (while all other casings of "record", are)
419 				const int len = this->keywordRE.matchedLength();
420 				setFormat(index, len, this->keywordFormat);
421 				index += len;
422 				continue;
423 			}
424 try_identifier: if (text.indexOf(this->identifierRE, index)==index) {
425 				const int len = this->identifierRE.matchedLength();
426 				setFormat(index, len, this->identifierFormat);
427 				index += len;
428 				continue;
429 			}
430 		}
431 		if (isdigit(firstChar) && text.indexOf(this->numericLiteralRE, index)==index) {
432 			const int len = this->numericLiteralRE.matchedLength();
433 			setFormat(index, len, this->numericLiteralFormat);
434 			index += len;
435 			continue;
436 		}
437 		if (text.indexOf(this->operatorRE, index)==index) {
438 			int len = this->operatorRE.matchedLength();
439 			if (text.indexOf(this->parenthesisRE, index)==index) {
440 				len = this->parenthesisRE.matchedLength();
441 				setFormat(index, len, this->operatorFormat);
442 			}
443 			index += len;
444 			continue;
445 		}
446 		if (this->highlightUnknownChars)
447 			setFormat(index, 1, this->unknownFormat);
448 		++index;
449 	}
450 }
451 
CocoaHighlighter(QTextDocument * parent,bool highlightUnknownChars,bool alwaysEnabled)452 CocoaHighlighter::CocoaHighlighter(QTextDocument *parent, bool highlightUnknownChars, bool alwaysEnabled) :
453 	QSyntaxHighlighter(parent),
454 	highlightUnknownChars(highlightUnknownChars),
455 	blanksRE("\\s+", Qt::CaseInsensitive, QRegExp::RegExp2),
456 	keywordRE(
457 			"[\\?]|\\b(?:Alias|And|Block|Break|Catch|Ciao|Clear|Continue|Define|"
458 			"DegLex|DegRevLex|Delete|Describe|Destroy|Do|Elif|Elim|"
459 			"Else|End|EndAlias|EndBlock|EndCatch|EndDefine|EndForeach|"
460 			"EndFor|EndFunc|EndIf|EndPackage|EndRepeat|EndTry|"
461 			"EndUsing|EndWhile|Exit|Export|False|Foreach|For|Func|If|"
462 			"ImportByRef|ImportByValue|In|IsDefined|IsIn|Lex|On|Opt|"
463 			"Ord|Or|Package|PosTo|PrintLn|Print|Protect|Quit|Record|"
464 			"Ref|Repeat|Return|Set|Skip|Source|Step|Then|Time|To|"
465 			"TopLevel|ToPos|True|Try|Unset|UponError|Until|Unprotect|"
466 			"Use|Using|Var|Weights|While|Xel)\\b", Qt::CaseInsensitive, QRegExp::RegExp2),
467 	constantRE("\\b(?:True|False|Lex|Xel|DegLex|DegRevLex|ToPos|PosTo)\\b", Qt::CaseInsensitive, QRegExp::RegExp2),
468 	singleQuoteliteralStringEndRE("(?:[^\"\\\\]|(?:\\\\(?:[nrta'\"\\\\]|(?:x[0-9a-fA-F]{2}))))*\'", Qt::CaseSensitive, QRegExp::RegExp2),
469 	doubleQuoteliteralStringEndRE("(?:[^\"\\\\]|(?:\\\\(?:[nrta'\"\\\\]|(?:x[0-9a-fA-F]{2}))))*\"", Qt::CaseSensitive, QRegExp::RegExp2),
470 	tripleQuoteliteralStringEndRE("(?:[^\"\\\\]|(?:\\\\(?:[nrta'\"\\\\]|(?:x[0-9a-fA-F]{2}))))*\"\"\"", Qt::CaseSensitive, QRegExp::RegExp2),
471 	identifierRE("[a-z$_][a-z0-9_]*", Qt::CaseInsensitive, QRegExp::RegExp2),
472 	commentEndRE("(?:[^\\*]|(?:\\*[^/]))*\\*/", Qt::CaseInsensitive, QRegExp::RegExp2),
473 	singlelineCommentEndRE("[^\\n]*", Qt::CaseInsensitive, QRegExp::RegExp2),
474 	numericLiteralRE("\\d+(?:\\.\\d+)?", Qt::CaseInsensitive, QRegExp::RegExp2),
475 	typeRE("\\b(?:BOOL|FUNCTION|LIST|INT|RAT|RECORD|TYPE|STRING|VOID|ERROR|OSTREAM"
476 			"RINGELEM|RATFUN|MODULEELEM|IDEAL|MODULE|MAT|RING|PACKAGE|RINGHOM|INTMAP)\\b", Qt::CaseSensitive, QRegExp::RegExp2),
477 	parenthesisRE("[()\\[\\]]|\\$\\{|\\}\\$", Qt::CaseInsensitive, QRegExp::RegExp2), // parenthesisRE is a quick-hack because QCodeEdit cannot highlight operators correctly
478 	operatorRE("[\\+\\-\\*/:<>=()\\[\\]|%^;,]|<=|>=|<>|:{1,2}=|<<|><|::|\\$\\{|\\}\\$|\\.{1,3}", Qt::CaseInsensitive, QRegExp::RegExp2),
479 	unclosedComment(false),
480 	unclosedStringLiteral(false),
481 	enabled(true),
482 	alwaysEnabled(alwaysEnabled)
483 {}
484 
setFormats(const ColorScheme & scheme)485 void CocoaHighlighter::setFormats(const ColorScheme &scheme) {
486 	this->literalStringFormat.setForeground(scheme.stringLiterals.foreground);
487 	if (scheme.stringLiterals.background.isValid())
488 		this->literalStringFormat.setBackground(scheme.stringLiterals.background);
489 	this->constantFormat.setForeground(scheme.constants.foreground);
490 	if (scheme.constants.background.isValid())
491 		this->constantFormat.setBackground(scheme.constants.background);
492 	this->commentFormat.setForeground(scheme.comments.foreground);
493 	if (scheme.comments.background.isValid())
494 		this->commentFormat.setBackground(scheme.comments.background);
495 	this->keywordFormat.setForeground(scheme.keywords.foreground);
496 	if (scheme.keywords.background.isValid())
497 		this->keywordFormat.setBackground(scheme.keywords.background);
498 	this->typeFormat.setForeground(scheme.types.foreground);
499 	if (scheme.types.background.isValid())
500 		this->typeFormat.setBackground(scheme.types.background);
501 	this->numericLiteralFormat.setForeground(scheme.numericalLiterals.foreground);
502 	if (scheme.numericalLiterals.background.isValid())
503 		this->numericLiteralFormat.setBackground(scheme.numericalLiterals.background);
504 	this->unknownFormat.setBackground(scheme.unknownChars.foreground);
505 	if (scheme.unknownChars.background.isValid())
506 		this->unknownFormat.setForeground(scheme.unknownChars.background);
507 }
508 
IdeErrorReporter(LexerNS::WarningSeverity warningLevel,Console * console,const boost::intrusive_ptr<IdeOutputStream> outputStream)509 IdeErrorReporter::IdeErrorReporter(LexerNS::WarningSeverity warningLevel, Console *console, const boost::intrusive_ptr<IdeOutputStream> outputStream) :
510 	ErrorReporter(warningLevel, outputStream),
511 	console(console)
512 {
513 	this->errorFormat.setFontWeight(QFont::Bold);
514 	this->errorFormat.setForeground(Qt::red);
515 	this->warningFormat.setFontWeight(QFont::Bold);
516 	this->warningFormat.setForeground(Qt::blue);
517 	this->calledByFormat.setFontWeight(QFont::Bold);
518 	this->whereFormat.setFontWeight(QFont::Bold);
519 	this->contextFormat.setFontWeight(QFont::Bold);
520 	this->boldFormat.setFontWeight(QFont::Bold);
521 	this->highlightErrorFormat.setUnderlineColor(Qt::red);
522 	this->highlightErrorFormat.setFontUnderline(true);
523 	this->highlightErrorFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline);
524 }
525 
closeEvent(QCloseEvent * event)526 void Console::closeEvent(QCloseEvent *event) {
527 	event->ignore();
528 	this->showMinimized();
529 }
530 
closeEvent(QCloseEvent * event)531 void Debugger::closeEvent(QCloseEvent *event) {
532 	event->ignore();
533 	this->showMinimized();
534 }
535 
IdeOutputStream(Console * console)536 IdeOutputStream::IdeOutputStream(Console *console) :
537 	OSTREAM(false, false),
538 	console(console)
539 {
540 	boldFormat.setFontWeight(QFont::Bold);
541 }
542 
execute(Console *)543 void FlushQC::execute(Console *) {
544 	assert(this_thread::get_id()==Console::guiThreadId);
545 	unique_lock<mutex> lock(this->mut);
546 	this->flushed = true;
547 	this->condVar.notify_one();
548 }
549 
550 intrusive_ptr<FlushQC> FlushQC::theInstance(new FlushQC);
551 
flush()552 void IdeOutputStream::flush() {
553 	assert(this_thread::get_id()==this->console->interpreterThreadId);
554 	intrusive_ptr<FlushQC> flushQC = FlushQC::theInstance;
555 	unique_lock<mutex> lock(flushQC->mut);
556 	flushQC->flushed = false;
557 	this->console->postCommand(flushQC);
558 	do
559 		flushQC->condVar.wait(lock);
560 	while (!flushQC->flushed);
561 }
562 
print(const string & s,bool highlight,QTextCharFormat format)563 void IdeOutputStream::print(const string &s, bool highlight, QTextCharFormat format) {
564 	this->console->postCommand(new PrintQC(s, highlight, format));
565 }
566 
print(const string & s)567 intrusive_ptr<OSTREAM> IdeOutputStream::print(const string &s) {
568 	this->console->postCommand(new PrintQC(s, false, normalFormat));
569 	return this;
570 }
571 
print(intrusive_ptr<const RightValue> v)572 intrusive_ptr<OSTREAM> IdeOutputStream::print(intrusive_ptr<const RightValue> v) {
573 	ostringstream ss;
574 	ss << v;
575 	this->console->postCommand(new PrintQC(ss.str(), false, normalFormat));
576 	return this;
577 }
578 
579 intrusive_ptr<PrintQC> PrintQC::newline(new PrintQC("\n", false, IdeOutputStream::normalFormat));
580 intrusive_ptr<PrintQC> PrintQC::newlineHL(new PrintQC("\n", true, IdeOutputStream::normalFormat));
581 
newline()582 intrusive_ptr<OSTREAM> IdeOutputStream::newline() {
583 	this->console->postCommand(PrintQC::newline);
584 	return this;
585 }
586 
newlineHL()587 intrusive_ptr<OSTREAM> IdeOutputStream::newlineHL() {
588 	this->console->postCommand(PrintQC::newlineHL);
589 	return this;
590 }
591 
execute(Console * console)592 void ClearReportedLocationsQC::execute(Console *console) {
593 	console->clearReportedLocations();
594 }
595 
596 intrusive_ptr<ClearReportedLocationsQC> ClearReportedLocationsQC::theInstance(new ClearReportedLocationsQC);
597 
doReadNextLine(const LexerStatus & ls,const ParserNS::ParserStatus & ps,string & chars)598 bool IdeLineProvider::doReadNextLine(const LexerStatus &ls, const ParserNS::ParserStatus &ps, string &chars)
599 {
600   assert(this_thread::get_id()==this->console->interpreterThreadId);
601   this->console->outputStream->print(prompt(ls, ps), true, IdeOutputStream::boldFormat);
602   unique_lock<mutex> lock(this->mut);
603   for(;;)
604   {
605     const Interpreter::InterpreterStatus status = this->console->interpreter->getStatus();
606     assert(status==Interpreter::IS_WAITING_FOR_COMMAND || status==Interpreter::IS_WAITING_FOR_COMMAND_COMPLETION);
607     if (!this->enteredLines.empty())
608     {
609       chars = this->enteredLines.front();
610       this->enteredLines.pop_front();
611       // if (!IsWhiteSpace(chars)) ...
612       if (chars.find_first_not_of(" \t") != string::npos)
613         this->console->interpreter->UpdateStatusToWFCC();
614       break;
615     }
616     if (status==Interpreter::IS_WAITING_FOR_COMMAND && !this->enteredFullCommands.empty())
617     {
618       chars = this->enteredFullCommands.front();
619       this->enteredFullCommands.pop_front();
620       this->console->postCommand(ClearReportedLocationsQC::theInstance);
621       break;
622     }
623     this->condVar.wait(lock);
624   }
625   this->console->outputStream->print(chars, true, IdeOutputStream::normalFormat);
626   this->console->outputStream->newline();
627   chars += '\n';
628   return true;
629 }
630 
enterLine(const string & line,bool isFullCommand)631 void IdeLineProvider::enterLine(const string &line, bool isFullCommand) {
632 	assert(this_thread::get_id()==Console::guiThreadId);
633 	unique_lock<mutex> lock(mut);
634 	(isFullCommand ? this->enteredFullCommands : this->enteredLines).push_back(line);
635 	this->condVar.notify_one();
636 }
637 
implReportWarning(const string & msg)638 void IdeErrorReporter::implReportWarning(const string &msg) {
639 	assert(this_thread::get_id()==this->console->interpreterThreadId);
640 	this->printWarning();
641 	this->outputStream->print(msg)->newline();
642 }
643 
outputHighlightedChars(const CharPointer & from,const CharPointer & to)644 void IdeErrorReporter::outputHighlightedChars(const CharPointer &from, const CharPointer &to) {
645 	assert(this_thread::get_id()==this->console->interpreterThreadId);
646 	assert(dynamic_pointer_cast<IdeOutputStream>(this->outputStream));
647 	IdeOutputStream * const ios = static_pointer_cast<IdeOutputStream>(this->outputStream).get();
648 	if (!*from) {
649 		ios->print("\n// <End of file>", true, IdeOutputStream::normalFormat);
650 		ios->newline();
651 		return;
652 	}
653 	intrusive_ptr<const Line> fromLine = from.getLine();
654 	intrusive_ptr<const Line> toLine = to.getLine();
655 	if (fromLine==toLine)
656 		return outputHighlightedLine(fromLine, from.getPositionInLine(), to.getPositionInLine(), false);
657 	if (!*to) {
658 		ios->print("\n// ... <End of file>", true, IdeOutputStream::normalFormat);
659 		this->outputStream->newline();
660 		return;
661 	}
662 	this->outputHighlightedLine(fromLine, from.getPositionInLine(), fromLine->chars.length()-2, true); // the last char of every line is \n and we don't want to underline it
663 	int skippedLines=0;
664 	intrusive_ptr<const Line> currLine = fromLine;
665 	while ( (currLine = currLine->getNextLineInBuffer())!= toLine )
666 		++skippedLines;
667 	if (skippedLines) {
668 		static_pointer_cast<IdeOutputStream>(this->outputStream)->print("// ... (", true, IdeOutputStream::normalFormat);
669 		if (skippedLines==1)
670 			ios->print("another source line");
671 		else
672 			ios->print("other ")->print(lexical_cast<string>(skippedLines))->print(" source lines");
673 		ios->print(") ...", true, IdeOutputStream::normalFormat);
674 	}
675 	ios->newlineHL();
676 	this->outputHighlightedLine(toLine, 0, to.getPositionInLine(), false);
677 }
678 
outputHighlightedLine(intrusive_ptr<const Line> line,size_t from,size_t to,bool keepsHilighting)679 void IdeErrorReporter::outputHighlightedLine(intrusive_ptr<const Line> line, size_t from, size_t to, bool keepsHilighting) {
680 	assert(this_thread::get_id()==this->console->interpreterThreadId);
681 	const size_t MAX_PREFIX = 40; // these are from the tty version, should we change them?!?
682 	const size_t MAX_SUFFIX = 30;
683 	assert(dynamic_pointer_cast<IdeOutputStream>(this->outputStream));
684 	IdeOutputStream * const ios = static_pointer_cast<IdeOutputStream>(this->outputStream).get();
685 	string prefix, suffix("\n");
686 	const string &chars = line->chars;
687     size_t printingStart=0, printingEnd=chars.length()==0 ? 0 : chars.length()-1;
688     if (from==to) {
689     	if (from>0)
690     		--from;
691     	if (to<printingEnd)
692     		++to;
693     }
694 	if (from>=MAX_PREFIX) {
695 		printingStart = from-MAX_PREFIX;
696 		prefix = "... ";
697 	}
698 	if ( (printingEnd-to)>=MAX_SUFFIX ) {
699 		printingEnd = to+MAX_SUFFIX;
700 		suffix = " ...\n";
701 	}
702 	assert(printingStart<=printingEnd);
703 	assert(printingEnd<chars.length());
704 	ios->print(prefix, keepsHilighting, IdeOutputStream::normalFormat);
705 	for(size_t i=printingStart; i<=printingEnd;++i) {
706 		char c = chars[i];
707 		if (c=='\n')
708 			break;
709 		ios->print(lexical_cast<string>(c=='\t' ? ' ':c), true, (from<=i && i<=to) ? this->highlightErrorFormat : IdeOutputStream::normalFormat);
710 	}
711 	ios->print(suffix, keepsHilighting, IdeOutputStream::normalFormat);
712 }
713 
implReportWarning(const string & msg,const CharPointer & from,const CharPointer & to)714 void IdeErrorReporter::implReportWarning(const string &msg, const CharPointer &from, const CharPointer &to) {
715 	assert(this_thread::get_id()==this->console->interpreterThreadId);
716 	this->printWarning();
717 	this->outputStream->print(msg);
718 	this->reportLineNumberWhenMeaningful(from, to, true, true);
719 	assert(dynamic_pointer_cast<IdeOutputStream>(this->outputStream));
720 	static_pointer_cast<IdeOutputStream>(this->outputStream)->print("\n", true, IdeOutputStream::normalFormat);
721 	this->outputHighlightedChars(from, to);
722 }
723 
implReportInterrupt(const LexerNS::CharPointer & from,const LexerNS::CharPointer & to)724 void IdeErrorReporter::implReportInterrupt(const LexerNS::CharPointer &from, const LexerNS::CharPointer &to) {
725 	assert(this_thread::get_id()==this->console->interpreterThreadId);
726 	this->outputStream->print("*** CoCoA-5 Interrupted ***")->newline();
727 	this->reportLineNumberWhenMeaningful(from, to, true, true);
728 	assert(dynamic_pointer_cast<IdeOutputStream>(this->outputStream));
729 	static_pointer_cast<IdeOutputStream>(this->outputStream)->print("\n", true, IdeOutputStream::normalFormat);
730 	this->outputHighlightedChars(from, to);
731 }
732 
implReportError(const string & msg)733 void IdeErrorReporter::implReportError(const string &msg) {
734 	assert(this_thread::get_id()==this->console->interpreterThreadId);
735 	this->printError();
736 	this->outputStream->print(msg)->newline();
737 }
738 
implReportError(const string & msg,const CharPointer & from,const CharPointer & to)739 void IdeErrorReporter::implReportError(const string &msg, const CharPointer &from, const CharPointer &to) {
740 	assert(this_thread::get_id()==this->console->interpreterThreadId);
741 	this->printError();
742 	this->outputStream->print(msg);
743 	this->reportLineNumberWhenMeaningful(from, to, true, true);
744 	assert(dynamic_pointer_cast<IdeOutputStream>(this->outputStream));
745 	static_pointer_cast<IdeOutputStream>(this->outputStream)->print("\n", true, IdeOutputStream::normalFormat);
746 	this->outputHighlightedChars(from, to);
747 }
748 
print(const string & s,bool highlight,QTextCharFormat format)749 void Console::print(const string &s, bool highlight, QTextCharFormat format) {
750 	assert(this_thread::get_id()==Console::guiThreadId);
751 	this->outputHL->enabled = highlight;
752 	QTextCursor cursor(this->outputTextEdit->document());
753 	cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
754 	cursor.setCharFormat(format);
755 	this->outputTextEdit->setTextCursor(cursor);
756 	cursor.insertText(QString::fromStdString(s));
757 	this->outputTextEdit->ensureCursorVisible();
758 }
759 
onEnterCommandClicked()760 void Console::onEnterCommandClicked()
761 {
762   assert(this_thread::get_id()==Console::guiThreadId);
763   const string input(this->inputTextEdit->toPlainText().toStdString());
764   if (input.length()==0)
765     return;
766   if (this->inputHL->thereIsUnclosedComment())
767   {
768     QMessageBox::critical(this, tr("Unclosed comment"), tr("The command cannot be entered because it contains an unclosed comment"), QMessageBox::Ok, QMessageBox::Ok);
769     return;
770   }
771   if (this->inputHL->thereIsUnclosedStringLiteral())
772   {
773     QMessageBox::critical(this, tr("Unclosed string literal"), tr("The command cannot be entered because it contains an unclosed string literal"), QMessageBox::Ok, QMessageBox::Ok);
774     return;
775   }
776   this->history.push_back(input);
777   if (this->history.size()>static_cast<deque<string>::size_type>(MAX_HISTORY))
778     this->history.pop_front();
779   this->currentHistoryPosition = static_cast<int>(this->history.size());
780   this->clearReportedLocations();
781   this->inputTextEdit->clear();
782   this->inputTextEdit->setFocus();
783   switch(this->interpreter->getStatus())
784   {
785   case Interpreter::IS_WAITING_FOR_COMMAND:
786   case Interpreter::IS_WAITING_FOR_COMMAND_COMPLETION:
787     break;
788   case Interpreter::IS_RUNNING:
789   case Interpreter::IS_RUNNING_BUILTIN:
790   case Interpreter::IS_PAUSED:
791     QMessageBox::warning(this, "Deferred executions", "The interpreter cannot run this command right away; your request has been queued and will be executed ASAP", QMessageBox::Ok);
792     break;
793   case Interpreter::IS_ENDED:
794     QMessageBox::warning(this, "Interpreter is not running anymore", "The interpreter is not running anymore, so it cannot execute anything", QMessageBox::Ok);
795     break;
796   }
797   if (this->debuggerCheckbox->isChecked())
798     this->interpreter->singleStepExecution = true;
799 
800   // Input may comprise several lines; pass them separately to lineProvider
801   string::size_type BOL = 0;
802   while (BOL < input.size())
803   {
804     const string::size_type EOL = input.find_first_of('\n', BOL);
805     this->lineProvider->enterLine(input.substr(BOL, EOL-BOL), false);
806     if (EOL == string::npos) break;
807     BOL = EOL+1;
808   }
809 }
810 
811 
reportLocation(const std::string & filename,int lineNumber,int columnNumber)812 int Console::reportLocation(const std::string &filename, int lineNumber, int columnNumber) {
813 	assert(this_thread::get_id()==Console::guiThreadId);
814 	for (const ReportedLocation &rl: this->reportedLocations)
815 		if (rl.filename==filename && rl.lineNumber==lineNumber && rl.columnNumber==columnNumber)
816 			return -1;
817 	this->reportedLocations.push_back(ReportedLocation(filename, lineNumber, columnNumber));
818 	return static_cast<int>(this->reportedLocations.size())-1;
819 }
820 
reportLineNumberWhenMeaningful(const CharPointer & fromPos,const CharPointer &,bool printColumns,bool includeHeader)821 bool IdeErrorReporter::reportLineNumberWhenMeaningful(const CharPointer &fromPos, const CharPointer & /*toPos*/, bool printColumns, bool includeHeader) {
822 	assert(this_thread::get_id()==this->console->interpreterThreadId);
823 	bool result = this->ErrorReporter::reportLineNumberWhenMeaningful(fromPos, fromPos, printColumns, includeHeader);
824 	if (result) {
825 		const intrusive_ptr<const Line> line = fromPos.getLine();
826 		const intrusive_ptr<const FileLineProvider> p = intrusive_ptr_cast<const FileLineProvider>(line->provider);
827 		this->console->postCommand(new ReportErrorQC(p->myFileName(), line->number, fromPos.getPositionInLine()));
828 	}
829 	return result;
830 }
831 
execute(Console * console)832 void ReportErrorQC::execute(Console *console) {
833 	assert(this_thread::get_id()==Console::guiThreadId);
834 	const int index = console->reportLocation(this->filename, this->lineNumber, this->columnNumber);
835 	if (index>=0) {
836 		console->locationComboBox->setEnabled(true);
837 		assert(this->lineNumber>=1);
838 		const string strippedFilename(QFileInfo(QString::fromStdString(this->filename)).baseName().toStdString());
839 		console->locationComboBox->addItem(QString::fromStdString("Line "+lexical_cast<string>(this->lineNumber)+" (col. "+ lexical_cast<string>(this->columnNumber+1) +") of "+strippedFilename), QVariant(index));
840 		console->openInEditorButton->setEnabled(true);
841 	}
842 }
843 
clearReportedLocations()844 void Console::clearReportedLocations() {
845 	assert(this_thread::get_id()==Console::guiThreadId);
846 	this->reportedLocations.clear();
847 	this->openInEditorButton->setEnabled(false);
848 	this->locationComboBox->clear();
849 	this->locationComboBox->setEnabled(false);
850 }
851 
postCommand(intrusive_ptr<QueuedCommand> command)852 void Console::postCommand(intrusive_ptr<QueuedCommand> command) {
853 	int nCommands;
854 	{
855 		lock_guard<mutex> lock(this->mut);
856 		this->commands.push_back(command);
857 		nCommands = this->commands.size();
858 	}
859 	const int tooManyQueuedCommands = /* arbitrarily set to... */ 16;
860 	if (nCommands>tooManyQueuedCommands && this_thread::get_id()==this->interpreterThreadId)
861 		this_thread::sleep(boost::posix_time::milliseconds(25));
862 }
863 
eventFilter(QObject * target,QEvent * event)864 bool Console::eventFilter(QObject *target, QEvent *event) {
865 	assert(this_thread::get_id()==Console::guiThreadId);
866 	if (target == this->inputTextEdit && event->type() == QEvent::KeyPress) {
867 		QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
868 		const int key = keyEvent->key();
869 		if ((keyEvent->modifiers() & Qt::ControlModifier)==Qt::ControlModifier) {
870 			if (key==Qt::Key_Up) {
871 				if (this->currentHistoryPosition>0) {
872 					this->inputTextEdit->setText(QString::fromStdString(this->history[--this->currentHistoryPosition]));
873 					goto cursorAtTheEnd;
874 				}
875 				return true;
876 			}
877 			if (key==Qt::Key_Down) {
878 				if (this->currentHistoryPosition < static_cast<int>((this->history.size()-1))) {
879 					this->inputTextEdit->setText(QString::fromStdString(this->history[++this->currentHistoryPosition]));
880 cursorAtTheEnd:
881 					QTextCursor cursor(this->inputTextEdit->document());
882 					cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
883 					this->inputTextEdit->setTextCursor(cursor);
884 				}
885 				return true;
886 			}
887 		} else if (key == Qt::Key_Return) {
888 			this->onEnterCommandClicked();
889 			return true;
890 		}
891 	}
892 	return QWidget::eventFilter(target, event);
893 }
894 
clearOutputWindow(bool AskUser)895 void Console::clearOutputWindow(bool AskUser) {
896 	assert(this_thread::get_id()==Console::guiThreadId);
897 	if (!AskUser || QMessageBox::question(this, tr("Deletion confirmation"), tr("Are you sure you want to delete the contents of the output window?"),
898                                               QMessageBox::Yes|QMessageBox::No, QMessageBox::No)==QMessageBox::Yes) {
899 		this->outputTextEdit->clear();
900 	}
901 }
902 
execute(Console * console)903 void PrintQC::execute(Console *console) {
904 	assert(this_thread::get_id()==Console::guiThreadId);
905 	console->print(this->s, this->highlight, this->format);
906 }
907 
908 namespace {
909 	class RunTheInterpreter {
910 		intrusive_ptr<Interpreter> interpreter;
911 		Console *console;
912 	public:
RunTheInterpreter(Console * console,intrusive_ptr<Interpreter> interpreter)913 		explicit RunTheInterpreter(Console *console, intrusive_ptr<Interpreter> interpreter) :
914 			interpreter(interpreter),
915 			console(console)
916 		{}
operator()917 		void operator()() {
918 			this->console->interpreterThreadId = this_thread::get_id();
919 			this->interpreter->run(console);
920 		}
921 	};
922 }
923 
Console(QWidget * parent,MainWindow * mainWindow,WarningSeverity warningLevel,bool warnAboutCocoa5,const vector<string> & packageList,bool fullCoCoALibError)924 Console::Console(QWidget *parent, MainWindow *mainWindow, WarningSeverity warningLevel, bool warnAboutCocoa5, const vector<string> & packageList, bool fullCoCoALibError) :
925 		QWidget(parent),
926 		currentHistoryPosition(-1),
927 		mainWindow(mainWindow),
928 		packageList(packageList),
929 		outputStream(new IdeOutputStream(this)),
930 		lineProvider(new IdeLineProvider(this)),
931 		errorReporter(new IdeErrorReporter(warningLevel, this, this->outputStream))
932 {
933 	guiThreadId = this_thread::get_id();
934 	this->setupUi(this);
935 	this->inputHL = new CocoaHighlighter(this->inputTextEdit->document(), true, true);
936 	this->outputHL = new CocoaHighlighter(this->outputTextEdit->document(), false, false);
937 	this->setHighlighterFormats();
938 	this->outputHL->enabled = false;
939 	connect(this->enterButton, SIGNAL(clicked()), this, SLOT(onEnterCommandClicked()));
940 	connect(this->clearOutputButton, SIGNAL(clicked()), this, SLOT(clearOutputWindow()));
941 	this->inputTextEdit->installEventFilter(this);
942 	this->interpreter = new Interpreter(warnAboutCocoa5, this->lineProvider, this->errorReporter, this->outputStream, fullCoCoALibError, 5000); // 5000 is "arbitrary" MaxStackSize
943 	boost::thread intThread(RunTheInterpreter(this, this->interpreter));
944 	QTimer *timer = new QTimer(this);
945 	connect(timer, SIGNAL(timeout()), this, SLOT(processCommands()));
946 	timer->start(20);
947 	connect(this->openInEditorButton, SIGNAL(clicked()), this, SLOT(onOpenInEditorClicked()));
948 	connect(this->pauseButton, SIGNAL(clicked()), this, SLOT(onPauseClicked()));
949 	connect(this->interruptButton, SIGNAL(clicked()), this, SLOT(onInterruptClicked()));
950 	this->packageLoadingProgressBar->setMinimum(0);
951 	this->packageLoadingProgressBar->setMaximum(packageList.size()+1);
952 	this->packageLoadingProgressBar->setValue(0);
953 }
954 
onOpenInEditorClicked()955 void Console::onOpenInEditorClicked() {
956 	assert(this_thread::get_id()==Console::guiThreadId);
957 	int currentIndex = this->locationComboBox->currentIndex();
958 	if (currentIndex<0)
959 		return;
960 	QVariant variant = this->locationComboBox->itemData(currentIndex);
961 	assert(variant.type()==QVariant::Int);
962 	int i = variant.toInt();
963 	assert(0<=i && i<static_cast<int>(this->reportedLocations.size()));
964 	QFileInfo file(QString::fromStdString(this->reportedLocations[i].filename));
965 	if (!file.exists()) {
966 		QMessageBox::critical(this, tr("File not found"), tr("Cannot find the selected file"), QMessageBox::Ok);
967 		return;
968 	}
969 	QString absPath(file.absoluteFilePath());
970 	SourceEditor *sourceEditor = this->mainWindow->editorFor(absPath, true);
971 	if (!sourceEditor) {
972 		sourceEditor = this->mainWindow->onFileNew();
973 		sourceEditor->load(absPath);
974 	}
975 	QEditor *const editor = sourceEditor->getEditor();
976 	const int lineNumber = this->reportedLocations[i].lineNumber;
977 	const int columnNumber = this->reportedLocations[i].columnNumber;
978 	assert(lineNumber>=1);
979 	editor->setCursor(QDocumentCursor(editor->document(), lineNumber - 1, columnNumber));
980 }
981 
editorFor(QString filename,bool activateWindow)982 SourceEditor *MainWindow::editorFor(QString filename, bool activateWindow) {
983 	assert(this_thread::get_id()==Console::guiThreadId);
984 	QString absPath = QFileInfo(filename).absoluteFilePath();
985 	QList<QMdiSubWindow *> wList = this->mdiArea->subWindowList();
986 	for (int i = 0; i < wList.size(); ++i) {
987 		QMdiSubWindow *const win = wList.at(i);
988 		SourceEditor *ed = dynamic_cast<SourceEditor *>(win->widget());
989 		if (ed && QFileInfo(ed->getEditor()->fileName()).absoluteFilePath()==absPath) {
990 			if (activateWindow) {
991 				win->raise();
992 				ed->getEditor()->setFocus();
993 			}
994 			return ed;
995 		}
996 	}
997 	return 0;
998 }
999 
closeEvent(QCloseEvent * e)1000 void MainWindow::closeEvent(QCloseEvent *e) {
1001 	assert(this_thread::get_id()==Console::guiThreadId);
1002 	if (this->interpreter->getStatus()!=Interpreter::IS_ENDED &&
1003 			QMessageBox::question(
1004 				this,
1005 				tr("Exit confirmation"),
1006 				tr("Are you sure you want to quit C5?"),
1007 				QMessageBox::Yes|QMessageBox::No,
1008 				QMessageBox::No)==QMessageBox::No) {
1009 		e->ignore();
1010 		return;
1011 	}
1012         this->console->clearOutputWindow(false); // false -> do not ask for confirmation
1013 	this->statusLabel->setText(" Exiting... "); // next cmd makes the update appear
1014         QMessageBox::information(this, "Ciao", "Ciao :-)");
1015 	this->mdiArea->closeAllSubWindows();
1016 	if (mdiArea->subWindowList().size()!=2 /* that is, the console and the debugger */)
1017 		e->ignore();
1018 	else {
1019 		if (this->interpreter->getStatus()==Interpreter::IS_ENDED)
1020 			QMessageBox::information(this, "Ciao", "Ciao :-)");
1021 		e->accept();
1022 	}
1023 }
1024 
findMainWindow(QObject * o)1025 MainWindow *MainWindow::findMainWindow(QObject *o) {
1026 	assert(this_thread::get_id()==Console::guiThreadId);
1027 	while (o) {
1028 		if (MainWindow *mw = dynamic_cast<MainWindow *>(o))
1029 			return mw;
1030 		o = o->parent();
1031 		assert(o);
1032 	}
1033 	return 0; // to make the compiler happy
1034 }
1035 
findConsole(QObject * w)1036 Console *MainWindow::findConsole(QObject *w) {
1037 	assert(this_thread::get_id()==Console::guiThreadId);
1038 	return findMainWindow(w)->console;
1039 }
1040 
processCommands()1041 void Console::processCommands() {
1042 	assert(this_thread::get_id()==Console::guiThreadId);
1043 	this->mainWindow->updateStatusLabel(); // it's important to do this before executing the commands (resume() uses flush to sync)
1044 	lock_guard<mutex> lock(this->mut);
1045 	while (!this->commands.empty()) {
1046 		this->commands.front()->execute(this);
1047 		this->commands.pop_front();
1048 	}
1049 }
1050 
1051 QTextCharFormat IdeOutputStream::normalFormat;
1052 QTextCharFormat IdeOutputStream::boldFormat;
1053 
printCalledBy()1054 void IdeErrorReporter::printCalledBy() {
1055 	assert(this_thread::get_id()==this->console->interpreterThreadId);
1056 	assert(dynamic_pointer_cast<IdeOutputStream>(this->outputStream));
1057 	static_pointer_cast<IdeOutputStream>(this->outputStream)->print(CalledbyPrefix, false, this->calledByFormat);
1058 }
1059 
printWhere()1060 void IdeErrorReporter::printWhere() {
1061 	assert(this_thread::get_id()==this->console->interpreterThreadId);
1062 	assert(dynamic_pointer_cast<IdeOutputStream>(this->outputStream));
1063 	static_pointer_cast<IdeOutputStream>(this->outputStream)->print(WherePrefix, false, this->whereFormat);
1064 }
1065 
printBold(const string & s)1066 void IdeErrorReporter::printBold(const string &s) {
1067 	assert(this_thread::get_id()==this->console->interpreterThreadId);
1068 	assert(dynamic_pointer_cast<IdeOutputStream>(this->outputStream));
1069 	static_pointer_cast<IdeOutputStream>(this->outputStream)->print(s, false, this->boldFormat);
1070 }
1071 
printWarning()1072 void IdeErrorReporter::printWarning() {
1073 	assert(this_thread::get_id()==this->console->interpreterThreadId);
1074 	assert(dynamic_pointer_cast<IdeOutputStream>(this->outputStream));
1075 	static_pointer_cast<IdeOutputStream>(this->outputStream)->print(WarningPrefix, false, this->warningFormat);
1076 }
1077 
printError()1078 void IdeErrorReporter::printError() {
1079 	assert(this_thread::get_id()==this->console->interpreterThreadId);
1080 	assert(dynamic_pointer_cast<IdeOutputStream>(this->outputStream));
1081 	static_pointer_cast<IdeOutputStream>(this->outputStream)->print(ErrorPrefix, false, this->errorFormat);
1082 }
1083 
printContext()1084 void IdeErrorReporter::printContext() {
1085 	assert(this_thread::get_id()==this->console->interpreterThreadId);
1086 	assert(dynamic_pointer_cast<IdeOutputStream>(this->outputStream));
1087 	static_pointer_cast<IdeOutputStream>(this->outputStream)->print(ContextPrefix, false, this->contextFormat);
1088 }
1089 
1090 namespace {
1091 
1092 	class PageUpDownCommand : public QEditorInputBinding::Command {
1093 		const bool up;
1094 		const QDocumentCursor::MoveMode mode;
1095 		public:
PageUpDownCommand(bool up,QDocumentCursor::MoveMode mode)1096 		PageUpDownCommand(bool up, QDocumentCursor::MoveMode mode) :
1097 			up(up),
1098 			mode(mode)
1099 		{}
exec(QEditor * e)1100 		void exec(QEditor *e) {
1101 			QDocumentCursor::MoveMode mode = this->mode;
1102 			if ( e->flag(QEditor::LineWrap) && e->flag(QEditor::CursorJumpPastWrap) )
1103 					mode |= QDocumentCursor::ThroughWrap;
1104 			if (up)
1105 				e->pageUp(mode);
1106 			else
1107 				e->pageDown(mode);
1108 		}
1109 	};
1110 
1111 	class EmacsBinding : public QEditorInputBinding {
id()1112 		QString id() const { return "emacs binding"; }
name()1113 		QString name() const { return this->id(); }
1114 	public:
EmacsBinding()1115 		EmacsBinding() {
1116 			this->setMapping(QKeySequence("Ctrl+A"), new QEditorInputBinding::MotionCommand(QDocumentCursor::StartOfLine, QDocumentCursor::MoveAnchor, 1));
1117 			this->setMapping(QKeySequence("Ctrl+E"), new QEditorInputBinding::MotionCommand(QDocumentCursor::EndOfLine, QDocumentCursor::MoveAnchor, 1));
1118 			this->setMapping(QKeySequence("Ctrl+V"), new PageUpDownCommand(false, QDocumentCursor::MoveAnchor));
1119 			this->setMapping(QKeySequence("Ctrl+Shift+V"), new PageUpDownCommand(false, QDocumentCursor::KeepAnchor));
1120 			this->setMapping(QKeySequence("Meta+V"), new PageUpDownCommand(true, QDocumentCursor::MoveAnchor));
1121 			this->setMapping(QKeySequence("Meta+Shift+V"), new PageUpDownCommand(true, QDocumentCursor::KeepAnchor));
1122 			// to add:
1123 			// Meta+W copy
1124 			// Ctrl+W cut
1125 			// Ctrl+Y paste
1126 			// Ctrl+_ undo
1127 			// Ctrl+S search
1128 			// Ctrl+X, Ctrl+S save
1129 		}
1130 	};
1131 
1132 }
1133 
applyTo(QFormatScheme * fScheme)1134 void ColorScheme::applyTo(QFormatScheme *fScheme) const {
1135 	fScheme->setFormat("stringLiteral", this->stringLiterals);
1136 	fScheme->setFormat("constant", this->constants);
1137 	fScheme->setFormat("comment", this->comments);
1138 	fScheme->setFormat("keyword", this->keywords);
1139 	fScheme->setFormat("type", this->types);
1140 	fScheme->setFormat("numericLiteral", this->numericalLiterals);
1141 	fScheme->setFormat("unknownChar", this->unknownChars);
1142 	fScheme->setFormat("braceMatch", this->braceMatch);
1143 	fScheme->setFormat("braceMismatch", this->braceMismatch);
1144 }
1145 
SourceEditor(MainWindow * parent)1146 SourceEditor::SourceEditor(MainWindow *parent) :
1147 		QWidget(parent),
1148 		menuAction(new QAction(this))
1149 {
1150 	assert(this_thread::get_id()==Console::guiThreadId);
1151 	this->setupUi(this);
1152 	const bool emacsLike = parent->actionEmacsLike->isChecked();
1153 	this->codeEdit = new QCodeEdit(!emacsLike, this);
1154 	QEditor * const ed = this->getEditor();
1155 	if (emacsLike) {
1156 		ed->addInputBinding(new EmacsBinding());
1157 	}
1158 	//this->codeEdit->addPanel("Line Mark Panel", QCodeEdit::West, true)->setShortcut(QKeySequence("F6"));
1159 	//this->codeEdit->addPanel("Line Number Panel", QCodeEdit::West, true)->setShortcut(QKeySequence("F11"));
1160 	//this->codeEdit->addPanel("Fold Panel", QCodeEdit::West, true)->setShortcut(QKeySequence("F9"));
1161 	this->codeEdit->addPanel("Line Change Panel", QCodeEdit::West, true);
1162 	this->codeEdit->addPanel("Status Panel", QCodeEdit::South, true);
1163 	this->codeEdit->addPanel("Goto Line Panel", QCodeEdit::South);
1164 	this->codeEdit->addPanel("Search Replace Panel", QCodeEdit::South);
1165 	this->outerVL->addWidget(ed);
1166 	QFormatScheme *fScheme = new QFormatScheme(this);
1167 	parent->getCurrentColorScheme()->applyTo(fScheme);
1168 	QLanguageFactory *lFactory = new QLanguageFactory(fScheme, this);
1169 	lFactory->addDefinitionPath(":/qxs");
1170 	lFactory->setLanguage(this->getEditor(), "a.cocoa5");
1171 	connect(this->saveButton, SIGNAL(clicked()), this, SLOT(save()));
1172 	connect(this->saveAsButton, SIGNAL(clicked()), this, SLOT(saveAs()));
1173 	connect(this->saveAndRunButton, SIGNAL(clicked()), this, SLOT(saveAndRun()));
1174 	connect(ed, SIGNAL(titleChanged(const QString&)), this, SLOT(onEditorTitleChanged(const QString&)));
1175 	connect(ed, SIGNAL(contentModified(bool)), this, SLOT(onEditorContentModified(bool)));
1176 	ed->setTitle("unnamed [*]");
1177     this->menuAction->setCheckable(true);
1178     connect(this->menuAction, SIGNAL(triggered()), ed, SLOT(setFocus()));
1179 }
1180 
onFileNew()1181 SourceEditor *MainWindow::onFileNew() {
1182 	assert(this_thread::get_id()==Console::guiThreadId);
1183 	SourceEditor *sourceEditor = new SourceEditor(this);
1184 	this->menuWindow->addAction(sourceEditor->menuAction);
1185 	this->menuWindowActionGroup->addAction(sourceEditor->menuAction);
1186 	QMdiSubWindow * const win = this->mdiArea->addSubWindow(sourceEditor);
1187 	win->setAttribute(Qt::WA_DeleteOnClose);
1188 	win->showNormal();
1189 	return sourceEditor;
1190 }
1191 
onEditorTitleChanged(const QString & title)1192 void SourceEditor::onEditorTitleChanged(const QString& title) {
1193 	assert(this_thread::get_id()==Console::guiThreadId);
1194 	this->setWindowTitle(title);
1195 	QString fn = this->getEditor()->fileName();
1196 	if (!fn.size())
1197 		fn = "unnamed";
1198 	this->menuAction->setText(fn);
1199 }
1200 
onEditorContentModified(bool y)1201 void SourceEditor::onEditorContentModified(bool y) {
1202 	assert(this_thread::get_id()==Console::guiThreadId);
1203 	this->setWindowModified(y);
1204 }
1205 
load(const QString & file)1206 void SourceEditor::load(const QString& file) {
1207 	assert(this_thread::get_id()==Console::guiThreadId);
1208 	QEditor *const ed = this->getEditor();
1209 	QString filename = file.count() ? QFileInfo(file).absoluteFilePath() : file;
1210 	if (filename.size() && QFile::exists(filename)) {
1211 		ed->load(filename);
1212 		// TODO: do something for recent files...
1213 		// updateRecentFiles(filename);
1214 		this->getEditor()->setTitle(QString("%1 [*]").arg(filename));
1215 	} else {
1216 		ed->setFileName("");
1217 		ed->setText("");
1218 		this->getEditor()->setTitle("unnamed [*]");
1219 	}
1220 }
1221 
save()1222 void SourceEditor::save() {
1223 	assert(this_thread::get_id()==Console::guiThreadId);
1224 	QEditor *const ed = this->getEditor();
1225 	if (ed->fileName().size())
1226 		ed->save();
1227 	else
1228 		this->saveAs();
1229 }
1230 
saveAndRun()1231 void SourceEditor::saveAndRun() {
1232 	assert(this_thread::get_id()==Console::guiThreadId);
1233 	this->save();
1234 	QEditor *const ed = this->getEditor();
1235 	if (ed->isContentModified())
1236 		return;
1237 	ostringstream ss;
1238 	intrusive_ptr<STRING> s(new STRING(ed->fileName().toStdString()));
1239 	s->dumpAsString(ss);
1240 	Console *console = MainWindow::findConsole(this);
1241 	if (console->interpreter->getStatus()!=Interpreter::IS_WAITING_FOR_COMMAND)
1242 		QMessageBox::warning(this, "Deferred executions", "The interpreter cannot run this file right away; your request has been queued and will be executed ASAP", QMessageBox::Ok);
1243 	console->lineProvider->enterLine(this->debuggerCheckbox->isChecked() ? "debug("+ss.str()+") ;" : "Source "+ss.str()+";", true);
1244 }
1245 
onFileSaveOutputWindow()1246 void MainWindow::onFileSaveOutputWindow() {
1247 	QString fn = QFileDialog::getSaveFileName(this, tr("Save output-windows contents as..."));
1248 	if (fn.isEmpty())
1249 		return;
1250 	QFile file(fn);
1251 	if (!file.open(QIODevice::WriteOnly)) {
1252 		QMessageBox::critical(this, "Cannot write", "Cannot open the file for writing", QMessageBox::Ok);
1253 		return;
1254 	}
1255 	if (file.write(this->console->outputTextEdit->toPlainText().toUtf8()) < 0)
1256 		QMessageBox::critical(this, "Cannot write", "Error while writing to the file", QMessageBox::Ok);
1257 	file.close();
1258 }
1259 
saveAs()1260 void SourceEditor::saveAs() {
1261 	assert(this_thread::get_id()==Console::guiThreadId);
1262 	QEditor *const ed = this->getEditor();
1263 	QString fn;
1264 	for(;;) {
1265 		fn = QFileDialog::getSaveFileName(this, tr("Save file as..."), ed->fileName(), tr("Sources (*.cocoa5 *.cpkg5)"));
1266 		if (fn.isEmpty())
1267 			return;
1268 		if (!(fn.endsWith(tr(".cocoa5")) || fn.endsWith(tr(".cpkg5"))))
1269 				fn.append(".cocoa5");
1270 		SourceEditor *otherEditor = MainWindow::findMainWindow(this)->editorFor(fn, false);
1271 		if (otherEditor==0 || otherEditor==this)
1272 			break;
1273 		QMessageBox::critical(this, "Name already in use", "This filename is already used by another edit-window; please choose another name", QMessageBox::Ok);
1274 	}
1275 	ed->save(fn);
1276 	this->setWindowTitle(QString("[%1[*]]").arg(fn));
1277 }
1278 
maybeSave()1279 bool SourceEditor::maybeSave() {
1280 	assert(this_thread::get_id()==Console::guiThreadId);
1281 	QEditor *const ed = this->getEditor();
1282 	if (ed->isContentModified()) {
1283 		int ret = QMessageBox::warning(
1284 							this,
1285 							tr("About to close"),
1286 							tr("The file %1 contains unsaved modifications.\nWould you like to save it?").arg(ed->fileName()),
1287 							QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
1288 		if (ret == QMessageBox::Cancel)
1289 			return true;
1290 		else if (ret == QMessageBox::Yes)
1291 			ed->save();
1292 	}
1293 	return false;
1294 }
1295 
closeEvent(QCloseEvent * e)1296 void SourceEditor::closeEvent(QCloseEvent *e) {
1297 	assert(this_thread::get_id()==Console::guiThreadId);
1298 	if ( this->maybeSave() ) {
1299 		e->ignore();
1300 		return;
1301 	}
1302 	this->QWidget::closeEvent(e);
1303 }
1304 
setHighlighterFormats()1305 void Debugger::setHighlighterFormats() {
1306 	this->codeHL->setFormats(*this->mainWindow->getCurrentColorScheme());
1307 	this->codeHL->rehighlight();
1308 }
1309 
Debugger(QWidget * parent,MainWindow * mainWindow)1310 Debugger::Debugger(QWidget *parent, MainWindow *mainWindow) :
1311 		QWidget(parent),
1312 		mainWindow(mainWindow)
1313 {
1314 	this->setupUi(this);
1315 	this->codeHL = new CocoaHighlighter(this->codeTextEdit->document(), false, true);
1316 	this->setHighlighterFormats();
1317 	connect(this->stepIntoButton, SIGNAL(clicked()), this, SLOT(onStepIntoClicked()));
1318 	connect(this->stepOverButton, SIGNAL(clicked()), this, SLOT(onStepOverClicked()));
1319 	connect(this->stepOutButton, SIGNAL(clicked()), this, SLOT(onStepOutClicked()));
1320 	connect(this->stepOutFnProcButton, SIGNAL(clicked()), this, SLOT(onStepOutFnProcClicked()));
1321 	connect(this->continueButton, SIGNAL(clicked()), this, SLOT(onContinueClicked()));
1322 	connect(this->stopButton, SIGNAL(clicked()), this, SLOT(onStopClicked()));
1323 	this->format.setBackground(QColor(255, 239, 0, 255));
1324 	connect(this->callStackList, SIGNAL(itemSelectionChanged()), this, SLOT(onCallStackSelectionChanged()));
1325 	connect(this->hideExportedNamesCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onHidingChoicesChanged()));
1326 	connect(this->hideFunctionsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onHidingChoicesChanged()));
1327 	connect(this->hideSystemNamesCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onHidingChoicesChanged()));
1328 	connect(this->hideTypesCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onHidingChoicesChanged()));
1329 	connect(this->autoFillLocalsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onAutoFillLocalsStateChanged()));
1330 	connect(this->fillLocalsButton, SIGNAL(clicked()), this, SLOT(onFillLocalsClicked()));
1331 }
1332 
onFillLocalsClicked()1333 void Debugger::onFillLocalsClicked() {
1334 	this->fillLocalsButton->setEnabled(false);
1335 	this->fillLocals();
1336 }
1337 
onAutoFillLocalsStateChanged()1338 void Debugger::onAutoFillLocalsStateChanged() {
1339 	if (this->autoFillLocalsCheckBox->isChecked()) {
1340 		this->fillLocals();
1341 		this->fillLocalsButton->setEnabled(false);
1342 	}
1343 	else
1344 		this->fillLocalsButton->setEnabled(true);
1345 }
1346 
onCallStackSelectionChanged()1347 void Debugger::onCallStackSelectionChanged() {
1348 	const int cRow = this->callStackList->currentRow();
1349 	const bool isTopLevel = cRow==(this->callStackList->count()-1);
1350 	const bool interpreterPaused = this->interpreter->getStatus() == Interpreter::IS_PAUSED;
1351 	this->stepIntoButton->setEnabled(interpreterPaused && cRow==0);
1352 	this->stepOverButton->setEnabled(interpreterPaused && cRow==0);
1353 	this->stepOutFnProcButton->setEnabled(interpreterPaused && cRow==0 && !isTopLevel);
1354 	this->stepOutButton->setEnabled(interpreterPaused && cRow==0);
1355 	this->continueButton->setEnabled(interpreterPaused);
1356 	this->stopButton->setEnabled(interpreterPaused);
1357 	this->hideExportedNamesCheckBox->setEnabled(isTopLevel);
1358 	this->hideSystemNamesCheckBox->setEnabled(isTopLevel);
1359 	this->fillCode();
1360 	if (this->autoFillLocalsCheckBox->isChecked()) {
1361 		this->fillLocalsButton->setEnabled(false);
1362 		this->fillLocals();
1363 	} else {
1364 		this->fillLocalsButton->setEnabled(true);
1365 		this->localsTree->clear();
1366 	}
1367 }
1368 
onHidingChoicesChanged()1369 void Debugger::onHidingChoicesChanged() {
1370 	if (this->autoFillLocalsCheckBox->isChecked())
1371 		this->fillLocals();
1372 	else
1373 		this->fillLocalsButton->setEnabled(true);
1374 }
1375 
addLocal(const string & name,const StaticEnv::VarData & varData,const Frame * currentFrame)1376 void Debugger::addLocal(const string &name, const StaticEnv::VarData &varData, const Frame *currentFrame) {
1377 	if (varData.depth<0)
1378 		return;
1379 	const QString qname(QString::fromStdString(name));
1380 	RuntimeEnvironment &re = this->interpreter->runtimeEnvironment;
1381 	const Frame *f = varData.debuggerTryToFindFrame(re.getTopLevelFrame(), currentFrame);
1382 	if (!f) {
1383 		this->addLocalError(qname, "Its environment does not exist anymore");
1384 		return;
1385 	}
1386 	if (varData.isCapturedValue) {
1387 		assert(f->userdefinedFun);
1388 		assert(static_cast<vector<intrusive_ptr<RightValue> >::size_type>(varData.index) < f->userdefinedFun->capturedValues.size());
1389 		const intrusive_ptr<RightValue> v(f->userdefinedFun->capturedValues[varData.index]);
1390 		if (dynamic_pointer_cast<FUNCTION>(v) && this->hideFunctionsCheckBox->isChecked())
1391 			return;
1392 		QTreeWidgetItem *item = new QTreeWidgetItem(this->localsTree);
1393 		item->setText(COL_NAME, qname);
1394 		item->setText(COL_KIND, "captured");
1395 		v->initTreeWidget(item);
1396 		this->localsTree->addTopLevelItem(item);
1397 		return;
1398 	}
1399 	this->addLocal(name, f, varData.index);
1400 }
1401 
addLocal(const std::string & name,const InterpreterNS::Frame * f,int index)1402 void Debugger::addLocal(const std::string &name, const InterpreterNS::Frame *f, int index) {
1403 	QString qname(QString::fromStdString(name));
1404 	assert(index>=0 && index<static_cast<int>(f->varSlots.size()));
1405 	const VariableSlot &vs = f->varSlots[index];
1406 	if (vs.isSystemProtected() && !vs.isIterationVariable() && this->hideSystemNamesCheckBox->isEnabled() && this->hideSystemNamesCheckBox->isChecked())
1407 		return;
1408 	if (vs.isPackage() && this->hideExportedNamesCheckBox->isEnabled() && this->hideExportedNamesCheckBox->isChecked())
1409 		return;
1410 	intrusive_ptr<Value> v = vs.value;
1411 	if (!v) { // this can only happen when a package has been reloaded (and some of the previous version members are not defined anymore) or an indeterminate has been removed because of a Use statement
1412 		assert(f==this->interpreter->runtimeEnvironment.getTopLevelFrame());
1413 		return;
1414 	}
1415 	intrusive_ptr<RightValue> rv;
1416 	try {
1417 		rv = v->asRightValue();
1418 	} catch (const InterruptException &) {
1419 		this->addLocalError(qname, "Evaluation has been interrupted");
1420 		return;
1421 	} catch (const RuntimeException &) {
1422 		this->addLocalError(qname, "Reference cannot be evaluated");
1423 		return;
1424 	}
1425 	if (dynamic_pointer_cast<TYPE>(rv) && this->hideTypesCheckBox->isChecked())
1426 		return;
1427 	if (dynamic_pointer_cast<FUNCTION>(rv) && this->hideFunctionsCheckBox->isChecked())
1428 		return;
1429 	QTreeWidgetItem *item = new QTreeWidgetItem(this->localsTree);
1430 	item->setText(COL_NAME, qname);
1431 	QString kind;
1432 	if (vs.isIterationVariable())
1433 		kind = "iteration";
1434 	else if (vs.isPackage())
1435 		kind = "package export";
1436 	else if (vs.isSystemProtected())
1437 		kind = "system";
1438 	else {
1439 		if (dynamic_pointer_cast<LeftValue>(v))
1440 			kind = vs.isProtected() ? "user protected, reference" : "reference";
1441 		else if (vs.isProtected())
1442 			kind = "user protected";
1443 	}
1444 	item->setText(COL_KIND, kind);
1445 	rv->initTreeWidget(item);
1446 	this->localsTree->addTopLevelItem(item);
1447 }
1448 
addLocalError(const QString & name,const QString & message)1449 void Debugger::addLocalError(const QString &name, const QString &message) {
1450 	QTreeWidgetItem *item = new QTreeWidgetItem(this->localsTree);
1451 	item->setText(COL_NAME, name);
1452 	item->setText(COL_VALUE, message);
1453 	this->localsTree->addTopLevelItem(item);
1454 }
1455 
update()1456 void Debugger::update() {
1457 	switch (this->interpreter->getStatus()) {
1458 	case Interpreter::IS_WAITING_FOR_COMMAND:
1459 	case Interpreter::IS_WAITING_FOR_COMMAND_COMPLETION:
1460 	case Interpreter::IS_PAUSED:
1461 	case Interpreter::IS_ENDED:
1462 		this->fillCallStack();
1463 		this->setEnabled(true);
1464 		break;
1465 	case Interpreter::IS_RUNNING:
1466 	case Interpreter::IS_RUNNING_BUILTIN:
1467 		this->setEnabled(false);
1468 		return;
1469 	}
1470 }
1471 
fillLocals()1472 void Debugger::fillLocals() {
1473 	this->localsTree->clear();
1474 	if (this->listedFrames.size()==0)
1475 		return;
1476 	int selected = this->callStackList->currentRow();
1477 	assert(selected>=0 && selected<static_cast<int>(this->listedFrames.size()));
1478 	RuntimeEnvironment &re = this->interpreter->runtimeEnvironment;
1479 	const Frame *const tlFrame = re.getTopLevelFrame();
1480 	const Frame *f = selected==0 ? re.getCurrentFrame() : (this->listedFrames[selected-1]-1);
1481 	for(;f!=tlFrame;--f) {
1482 		assert(f>tlFrame);
1483 		intrusive_ptr<StaticEnv> env = f->block->staticEnv;
1484 		assert(env);
1485 		assert(!f->userdefinedFun || f->userdefinedFun->fnDecl->staticEnv==f->block->staticEnv);
1486 		for(StaticEnv::IdMap::const_iterator it = env->identifierMap.begin(); it!=env->identifierMap.end(); ++it)
1487 			this->addLocal(it->first, it->second, f);
1488 		if (f->userdefinedFun)
1489 			break;
1490 	}
1491 	if (f==tlFrame) {
1492 		for(map<string, int>::const_iterator it = re.topLevelIdentifiers.begin(); it!=re.topLevelIdentifiers.end(); ++it) {
1493 			assert(it->first.length());
1494 			assert(it->second>=0 && it->second<static_cast<int>(tlFrame->varSlots.size()));
1495 			if (it->first[0]!='$')
1496 				this->addLocal(it->first, f, it->second);
1497 		}
1498 	}
1499 }
1500 
fillCode()1501 void Debugger::fillCode() {
1502 	if (this->listedFrames.size()==0 || this->interpreter->getStatus()!=Interpreter::IS_PAUSED) {
1503 		this->codeTextEdit->clear();
1504 		this->codeGroupBox->setTitle("Code");
1505 		return;
1506 	}
1507 	int selected = this->callStackList->currentRow();
1508 	assert(selected>=0 && selected<static_cast<int>(this->listedFrames.size()));
1509 	CharPointer begin(this->interpreter->pausedOn->getBegin());
1510 	CharPointer end(this->interpreter->pausedOn->getEnd());
1511 	if (selected) {
1512 		const Frame *f = this->listedFrames[selected-1];
1513 		assert(f->userdefinedFun);
1514 		assert(f->invocationExp);
1515 		begin = f->invocationExp->getBegin();
1516 		end = f->invocationExp->getEnd();
1517 	}
1518 	const QTextCharFormat oldFormat = this->codeTextEdit->currentCharFormat();
1519         const intrusive_ptr<const LineProvider> provider = begin.getLine()->provider;
1520 	if (provider->IamReadingFromFile()) {
1521           intrusive_ptr<const FileLineProvider> fileProvider = dynamic_pointer_cast<const FileLineProvider>(begin.getLine()->provider); // NASTY HACK
1522           this->codeGroupBox->setTitle(QString::fromStdString(provider->myFileName()));
1523 		this->codeTextEdit->setText(QString::fromStdString(fileProvider->wholeFile));
1524 		QTextCursor c(this->codeTextEdit->document());
1525 		c.setPosition(0);
1526 		const int beginLineNumber = begin.getLine()->number;
1527 		const int endLineNumber = end.getLine()->number;
1528 		assert(beginLineNumber>=1);
1529 		assert(endLineNumber>=beginLineNumber);
1530 		c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, beginLineNumber-1);
1531 		c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, static_cast<int>(begin.getPositionInLine()));
1532 		c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
1533 		c.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor, endLineNumber - beginLineNumber);
1534 		c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, static_cast<int>(end.getPositionInLine())+1);
1535 		c.mergeCharFormat(this->format);
1536 		c.clearSelection();
1537 		c.setCharFormat(oldFormat);
1538 		this->codeTextEdit->setTextCursor(c);
1539 	} else {
1540 		this->codeGroupBox->setTitle("Top-level code");
1541 		this->codeTextEdit->clear();
1542 		intrusive_ptr<const Line> line = begin.getLine();
1543 		this->codeTextEdit->append(QString::fromStdString(line->chars));
1544 		QTextCursor c(this->codeTextEdit->document());
1545 		c.setPosition(0);
1546 		c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, static_cast<int>(begin.getPositionInLine()));
1547 		c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
1548 		intrusive_ptr<const Line> endLine = end.getLine();
1549 		for(;;) {
1550 			if (line==endLine) {
1551 				c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, static_cast<int>(end.getPositionInLine())+1);
1552 				break;
1553 			}
1554 			line = line->getNextLineInBuffer();
1555 			this->codeTextEdit->append(QString::fromStdString(line->chars));
1556 			c.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor);
1557 		}
1558 		c.mergeCharFormat(this->format);
1559 		c.clearSelection();
1560 		c.setCharFormat(oldFormat);
1561 		this->codeTextEdit->setTextCursor(c);
1562 	}
1563 	this->codeTextEdit->ensureCursorVisible();
1564 }
1565 
fillCallStack()1566 void Debugger::fillCallStack() {
1567 	this->listedFrames.clear();
1568 	this->callStackList->clear();
1569 	RuntimeEnvironment &re = this->interpreter->runtimeEnvironment;
1570 	const Frame *currentFrame = re.getCurrentFrame();
1571 	const Frame *tlFrame = re.getTopLevelFrame();
1572 	for(;currentFrame>tlFrame;--currentFrame) {
1573 		if (currentFrame->userdefinedFun) {
1574 			this->listedFrames.push_back(currentFrame);
1575 			string name(currentFrame->userdefinedFun->fnDecl->fnName);
1576 			if (name.empty())
1577 				name = "<anonymous function>";
1578 			this->callStackList->addItem(QString::fromStdString("Fn-proc "+name));
1579 		}
1580 	}
1581 	this->callStackList->addItem("Top-level");
1582 	assert(!tlFrame->userdefinedFun);
1583 	this->listedFrames.push_back(tlFrame);
1584 	this->callStackList->setCurrentRow(0);
1585 }
1586 
clearAndDisable()1587 void Debugger::clearAndDisable() {
1588 	this->setEnabled(false);
1589 	this->callStackList->clear();
1590 	this->localsTree->clear();
1591 	this->codeTextEdit->clear();
1592 }
1593 
onStepIntoClicked()1594 void Debugger::onStepIntoClicked() {
1595 	this->clearAndDisable();
1596 	this->interpreter->resume();
1597 }
1598 
onStepOverClicked()1599 void Debugger::onStepOverClicked() {
1600 	this->clearAndDisable();
1601 	this->interpreter->singleStepExecution = false;
1602 	this->interpreter->doStepOver = true;
1603 	this->interpreter->resume();
1604 }
1605 
onStepOutFnProcClicked()1606 void Debugger::onStepOutFnProcClicked() {
1607 	this->clearAndDisable();
1608 	Frame *f = this->interpreter->runtimeEnvironment.getCurrentFunctionFrameOrNull();
1609 	assert(f);
1610 	if (f) {
1611 		f->singleStepOnPop = true;
1612 		this->interpreter->singleStepExecution = false;
1613 	}
1614 	this->interpreter->resume();
1615 }
1616 
onStepOutClicked()1617 void Debugger::onStepOutClicked() {
1618 	this->clearAndDisable();
1619 	this->interpreter->runtimeEnvironment.getCurrentFrame()->singleStepOnPop = true;
1620 	this->interpreter->singleStepExecution = false;
1621 	this->interpreter->resume();
1622 }
1623 
onContinueClicked()1624 void Debugger::onContinueClicked() {
1625 	this->clearAndDisable();
1626 	this->interpreter->singleStepExecution = false;
1627 	this->interpreter->resume();
1628 }
1629 
onStopClicked()1630 void Debugger::onStopClicked()
1631 {
1632   this->clearAndDisable();
1633   this->interpreter->singleStepExecution = false;
1634   CoCoA::SetSignalReceived(2/*SIGINT*/); // actual signal does not matter (I believe)
1635   this->interpreter->resume();
1636 }
1637 
initColorSchemes()1638 void MainWindow::initColorSchemes() {
1639 	static bool done;
1640 	if (done)
1641 		return;
1642 	// Anna's color scheme
1643 	csAnna.stringLiterals.foreground = QColor::fromRgb(0, 0x8b, 0);
1644 	csAnna.constants.foreground = QColor::fromRgb(0xcd, 0x85, 0);
1645 	csAnna.comments.foreground = QColor::fromRgb(0xcd, 0, 0);
1646 	csAnna.keywords.foreground = QColor::fromRgb(0x36, 0x64, 0x8b);
1647 	csAnna.types.foreground = QColor::fromRgb(0xcd, 0x69, 0xc9);
1648 	csAnna.numericalLiterals.foreground = QColor::fromRgb(0, 0, 0);
1649 	csAnna.unknownChars.foreground = QColor::fromRgb(0xff, 0xff, 0xff);
1650 	csAnna.unknownChars.background = QColor::fromRgb(0xff, 0, 0);
1651 	csAnna.braceMatch.foreground = QColor::fromRgb(0x8b, 0, 0x8b);
1652 	csAnna.braceMatch.background = QColor::fromRgb(0xff, 0xff, 0);
1653 	csAnna.braceMismatch.foreground = QColor::fromRgb(0xff, 0xff, 0xff);
1654 	csAnna.braceMismatch.background = QColor::fromRgb(0xff, 0, 0);
1655 	// Gio's color scheme
1656 	csGio.stringLiterals.foreground = QColor::fromRgb(0xff, 0, 0x90);
1657 	csGio.constants.foreground = QColor::fromRgb(0, 0, 0xff);
1658 	csGio.comments.foreground = QColor::fromRgb(0, 0x70, 0);
1659 	csGio.keywords.foreground = QColor::fromRgb(0, 0, 0xff);
1660 	csGio.types.foreground = QColor::fromRgb(0x64, 0x95, 0xed);
1661 	csGio.numericalLiterals.foreground = QColor::fromRgb(0, 0, 0x40);
1662 	csGio.unknownChars.foreground = QColor::fromRgb(0xff, 0xff, 0xff);
1663 	csGio.unknownChars.background = QColor::fromRgb(0xff, 0, 0);
1664 	csGio.braceMatch.foreground = QColor::fromRgb(0x8b, 0, 0x8b);
1665 	csGio.braceMatch.background = QColor::fromRgb(0xff, 0xff, 0);
1666 	csGio.braceMismatch.foreground = QColor::fromRgb(0xff, 0xff, 0xff);
1667 	csGio.braceMismatch.background = QColor::fromRgb(0xff, 0, 0);
1668 	done = true;
1669 }
1670 
setHighlighterFormats()1671 void Console::setHighlighterFormats() {
1672 	const ColorScheme *const cs = this->mainWindow->getCurrentColorScheme();
1673 	this->inputHL->setFormats(*cs);
1674 	this->inputHL->rehighlight();
1675 	this->outputHL->setFormats(*cs);
1676 	this->outputHL->rehighlight();
1677 }
1678 
setHighlighterFormats()1679 void MainWindow::setHighlighterFormats() {
1680 	QList<QMdiSubWindow *> wList = this->mdiArea->subWindowList();
1681 	for (int i = 0; i < wList.size(); ++i) {
1682 		QMdiSubWindow *const win = wList.at(i);
1683 		if (SourceEditor *se = dynamic_cast<SourceEditor *>(win->widget())) {
1684 			QEditor * const ed = se->getEditor();
1685 			this->currentColorScheme->applyTo(ed->document()->formatScheme());
1686 			ed->highlight();
1687 		}
1688 	}
1689 	this->console->setHighlighterFormats();
1690 	this->debugger->setHighlighterFormats();
1691 }
1692 
onAnnaCS()1693 void MainWindow::onAnnaCS() {
1694 	this->currentColorScheme = &csAnna;
1695 	this->setHighlighterFormats();
1696 	QSettings settings;
1697 	settings.setValue(SETTING_COLORSCHEME_NAME, "Anna");
1698 }
1699 
onGioCS()1700 void MainWindow::onGioCS() {
1701 	this->currentColorScheme = &csGio;
1702 	this->setHighlighterFormats();
1703 	QSettings settings;
1704 	settings.setValue(SETTING_COLORSCHEME_NAME, "Gio");
1705 }
1706 
1707 ColorScheme MainWindow::csAnna;
1708 ColorScheme MainWindow::csGio;
1709 
MainWindow(QWidget * parent,QApplication * application,WarningSeverity warningLevel,bool warnAboutCocoa5,const vector<string> & packageList,bool fullCoCoALibError)1710 MainWindow::MainWindow(QWidget *parent, QApplication *application, WarningSeverity warningLevel, bool warnAboutCocoa5, const vector<string> & packageList, bool fullCoCoALibError) :
1711 		QMainWindow(parent),
1712 		application(application),
1713 		mdiArea(new QMdiArea(this)),
1714 		oldInterpreterStatus(static_cast<Interpreter::InterpreterStatus>(-1)),
1715 		statusLabel(new QLabel(this)),
1716 		menuWindowActionGroup(new QActionGroup(this)),
1717 		colorSchemeActionGroup(new QActionGroup(this)),
1718 		actionMenuConsole(new QAction("&Console",this)),
1719 		actionMenuDebugger(new QAction("&Debugger", this)),
1720 		currentColorScheme(&csAnna)
1721 {
1722 	initColorSchemes();
1723 	this->setupUi(this);
1724 	this->setCentralWidget(this->mdiArea);
1725 	this->actionMenuConsole->setIcon(QIcon(":/images/utilities-terminal.png"));
1726 	this->actionMenuDebugger->setIcon(QIcon(":/images/utilities-system-monitor.png"));
1727 	this->toolBar->addAction(this->actionMenuConsole);
1728 	this->toolBar->addAction(this->actionMenuDebugger);
1729 	QSettings settings;
1730 	if (settings.value(SETTING_COLORSCHEME_NAME, "Anna")=="Gio")
1731 		this->currentColorScheme = &csGio;
1732 	if (settings.value(SETTING_EMACS_LIKE_KEYBINDINGS, false).toBool())
1733 		this->actionEmacsLike->setChecked(true);
1734 	this->menuWindowActionGroup->addAction(this->actionMenuConsole);
1735 	this->menuWindowActionGroup->addAction(this->actionMenuDebugger);
1736 	this->colorSchemeActionGroup->addAction(this->actionAnna);
1737 	this->actionAnna->setChecked(true);
1738 	connect(this->actionAnna, SIGNAL(triggered()), this, SLOT(onAnnaCS()));
1739 	this->colorSchemeActionGroup->addAction(this->actionGio);
1740 	connect(this->actionGio, SIGNAL(triggered()), this, SLOT(onGioCS()));
1741 	this->statusBar()->addWidget(this->statusLabel);
1742 	this->menuWindow->addAction(this->actionMenuConsole);
1743 	this->menuWindow->addAction(this->actionMenuDebugger);
1744 	this->console = new Console(this, this, warningLevel, warnAboutCocoa5, packageList, fullCoCoALibError);
1745 	this->debugger = new Debugger(this, this);
1746 	this->debuggerMdiSubWin = this->mdiArea->addSubWindow(this->debugger);
1747 	this->debuggerMdiSubWin->setWindowIcon(this->debugger->windowIcon());
1748 	this->debuggerMdiSubWin->showMinimized();
1749 	this->actionMenuDebugger->setCheckable(true);
1750 	connect(this->actionMenuDebugger, SIGNAL(triggered()), this->debuggerMdiSubWin, SLOT(setFocus()));
1751 	this->interpreter = this->debugger->interpreter = this->console->interpreter.get();
1752 	this->consoleMdiSubWin = this->mdiArea->addSubWindow(this->console);
1753 	this->consoleMdiSubWin->setWindowIcon(this->console->windowIcon());
1754 	this->actionMenuConsole->setCheckable(true);
1755 	connect(this->actionMenuConsole, SIGNAL(triggered()), this->console->inputTextEdit, SLOT(setFocus()));
1756 	this->consoleMdiSubWin->show();
1757 	connect(this->actionNew, SIGNAL(triggered()), this, SLOT(onFileNew()));
1758 	connect(this->actionOpen, SIGNAL(triggered()), this, SLOT(onFileOpen()));
1759 	connect(this->actionExit, SIGNAL(triggered()), this, SLOT(onFileExit()));
1760 	connect(this->actionClose, SIGNAL(triggered()), this, SLOT(onWindowClose()));
1761 	connect(this->actionClose_All, SIGNAL(triggered()), this, SLOT(onWindowCloseAll()));
1762 	connect(this->actionTile, SIGNAL(triggered()), this, SLOT(onWindowTile()));
1763 	connect(this->actionCascade, SIGNAL(triggered()), this, SLOT(onWindowCascade()));
1764 	connect(this->actionNext, SIGNAL(triggered()), this, SLOT(onWindowNext()));
1765 	connect(this->actionPrevious, SIGNAL(triggered()), this, SLOT(onWindowPrevious()));
1766 	connect(this->actionAbout, SIGNAL(triggered()), this, SLOT(onHelpAbout()));
1767 	connect(this->actionFont, SIGNAL(triggered()), this, SLOT(onOptionsFont()));
1768 	connect(this->actionEmacsLike, SIGNAL(triggered()), this, SLOT(onOptionsEmacsLike()));
1769 	connect(this->actionFileSaveOutputWindow, SIGNAL(triggered()), this, SLOT(onFileSaveOutputWindow()));
1770 	connect(this->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(onSubWindowActivated(QMdiSubWindow*)));
1771 }
1772 
onOptionsFont()1773 void MainWindow::onOptionsFont() {
1774 	 bool ok;
1775 	 QFont font = QFontDialog::getFont(&ok, this);
1776 	 if (ok) {
1777 		 QDocument::setFont(font);
1778 	     this->application->setFont(font);
1779 	     QSettings settings;
1780 	     settings.setValue(SETTING_FONT_FAMILY, font.family());
1781 	     settings.setValue(SETTING_FONT_POINTSIZE, font.pointSize());
1782 	     settings.setValue(SETTING_FONT_WEIGHT, font.weight());
1783 	     settings.setValue(SETTING_FONT_ITALIC, font.italic());
1784 	 }
1785 }
1786 
onOptionsEmacsLike()1787 void MainWindow::onOptionsEmacsLike() {
1788 	 QSettings settings;
1789 	 settings.setValue(SETTING_EMACS_LIKE_KEYBINDINGS, this->actionEmacsLike->isChecked());
1790 	 if (this->mdiArea->subWindowList().size()!=2 /* that is, console and debugger */)
1791 		 QMessageBox::information(this, "Warning", "Note: this choice affects only newly-opened editors, already opened editors keep their key-bindings.");
1792 }
1793 
1794 intrusive_ptr<IncrementProgressBarQC> IncrementProgressBarQC::theInstance(new IncrementProgressBarQC);
1795 
execute(Console * console)1796 void IncrementProgressBarQC::execute(Console *console) {
1797 	QProgressBar *bar = console->packageLoadingProgressBar;
1798 	const int newValue = bar->value()+1;
1799 	bar->setValue(newValue);
1800 	if (newValue==bar->maximum()) {
1801 		console->bottomLeftVL->removeWidget(console->packageLoadingProgressBar);
1802 		console->inputTextEdit->setEnabled(true);
1803 		console->enterButton->setEnabled(true);
1804 		delete console->packageLoadingProgressBar;
1805 		console->packageLoadingProgressBar = 0;
1806 		console->inputTextEdit->setFocus();
1807 	}
1808 }
1809 
myLoadPackages()1810 bool Console::myLoadPackages() {
1811 	bool result = true;
1812 	this->outputStream->print("", false, IdeOutputStream::normalFormat); // disable the HL for the following lines
1813         if (this->packageList.empty())
1814         {
1815           string LINE; LINE.resize(47, '-');  // 47 because it looks right on my screen
1816           this->outputStream->print(LINE)->newline();
1817           this->outputStream->print(PACKAGES_NOT_FOUND)->newline();
1818           this->outputStream->print(LINE)->newline();
1819         }
1820 	for (const string &fullname: this->packageList)
1821         {
1822           this->postCommand(IncrementProgressBarQC::theInstance);
1823           if (result)
1824           {
1825 // 2015-07-28 JAA			this->outputStream->print(PACKAGE_AUTOLOAD_LOADING, false, IdeOutputStream::normalFormat);
1826 // 2015-07-28 JAA			this->outputStream->print(fullname)->newline();
1827             interpreter->readAndExecute(fullname, true, true);
1828             if (interpreter->errorReporter->getErrorCount())
1829             {
1830 // JAA 2015-07-29				this->outputStream->print(PACKAGE_AUTOLOAD_FAILURE_MESSAGE)->flush();
1831               result = false;
1832             }
1833           }
1834           // 2015-07-28 suppress printing of "skipping package..."
1835           // else
1836           // {
1837           // 	this->outputStream->print(PACKAGE_AUTOLOAD_SKIPPING_PKG_DUE_TO_FAILURE, false, IdeOutputStream::normalFormat);
1838           // 	this->outputStream->print(fullname)->newline();
1839           // }
1840 	}
1841   this->outputStream->print(CoCoA5BannerNonFixedWidthFonts())->newline();
1842   if (!result)
1843     this->outputStream->print(PACKAGE_AUTOLOAD_FAILURE_MESSAGE)->flush();
1844   this->postCommand(IncrementProgressBarQC::theInstance);
1845   return result;
1846 }
1847 
onSubWindowActivated(QMdiSubWindow * activatedWindow)1848 void MainWindow::onSubWindowActivated(QMdiSubWindow *activatedWindow) {
1849 	//cout << "activatedWindow " << static_cast<void *>(activatedWindow) << endl;
1850 	const bool thereIsWin = activatedWindow!=0;
1851 	this->actionClose->setEnabled(thereIsWin && activatedWindow!=this->consoleMdiSubWin && activatedWindow!=this->debuggerMdiSubWin);
1852 	if (thereIsWin) {
1853 		if (activatedWindow==this->consoleMdiSubWin) {
1854 			this->actionMenuConsole->setChecked(true);
1855 			this->console->inputTextEdit->setFocus();
1856 		}
1857 		else if (activatedWindow==this->debuggerMdiSubWin)
1858 			this->actionMenuDebugger->setChecked(true);
1859 		else if (SourceEditor *ed = dynamic_cast<SourceEditor *>(activatedWindow->widget())) {
1860 			ed->menuAction->setChecked(true);
1861 			ed->getEditor()->setFocus();
1862 		} else {
1863 			assert(false); // what's going on?!?
1864 		}
1865 	}
1866 }
1867 
updateStatusLabel()1868 void MainWindow::updateStatusLabel() {
1869 	assert(this_thread::get_id()==Console::guiThreadId);
1870 	const Interpreter::InterpreterStatus status = this->console->interpreter->getStatus();
1871 	if (status==this->oldInterpreterStatus)
1872 		return;
1873 	QString message;
1874 	bool buttonsEnabled = false;
1875 	switch ((this->oldInterpreterStatus = status)) {
1876 	case Interpreter::IS_WAITING_FOR_COMMAND:
1877 		this->interpreter->singleStepExecution = false;
1878 		if (this->consoleMdiSubWin->isMinimized())
1879 				this->consoleMdiSubWin->showNormal();
1880 		this->console->inputTextEdit->setFocus();
1881 		message = "The interpreter is <b>waiting</b> for a command";
1882 		break;
1883 	case Interpreter::IS_WAITING_FOR_COMMAND_COMPLETION:
1884 		message = "The interpreter is <b>waiting</b> for the ending of the current command";
1885 		break;
1886 	case Interpreter::IS_RUNNING:
1887 		message = "The interpreter is <b>running</b>";
1888 		buttonsEnabled = true;
1889 		break;
1890 	case Interpreter::IS_RUNNING_BUILTIN:
1891 		message = "The interpreter is <b>running</b> a built-in function";
1892 		buttonsEnabled = true;
1893 		break;
1894 	case Interpreter::IS_PAUSED:
1895 		message = "The interpreter is <b>paused</b>";
1896 		if (this->debuggerMdiSubWin->isMinimized())
1897 			this->debuggerMdiSubWin->showNormal();
1898 		this->debuggerMdiSubWin->setFocus();
1899 		break;
1900 	case Interpreter::IS_ENDED:
1901 		message = "The interpreter has <b>quit</b>";
1902 		this->onFileExit();
1903 		break;
1904 	}
1905 	this->console->pauseButton->setEnabled(buttonsEnabled);
1906 	this->console->interruptButton->setEnabled(buttonsEnabled);
1907 	this->statusLabel->setText(message);
1908 	this->debugger->update();
1909 }
1910 
onPauseClicked()1911 void Console::onPauseClicked() {
1912 	this->interpreter->singleStepExecution = true;
1913 }
1914 
onInterruptClicked()1915 void Console::onInterruptClicked()
1916 {
1917   if (QMessageBox::question(
1918         this,
1919         tr("Interrupt confirmation"),
1920         tr("Are you sure you want to abort the current computation?\n\nNote: built-in fn-procs cannot be interrupted, so it might take a while before the computation actually halts"),
1921         QMessageBox::Yes|QMessageBox::No,
1922         QMessageBox::No)==QMessageBox::Yes)
1923     CoCoA::SetSignalReceived(2/*SIGINT*/); // actual signal does not matter (I believe)
1924 }
1925 
onFileOpen()1926 void MainWindow::onFileOpen() {
1927 	assert(this_thread::get_id()==Console::guiThreadId);
1928 	QString fn = QFileDialog::getOpenFileName(this, "Open CoCoA 5 source...", "", tr("Sources (*.cocoa5 *.cpkg5)"));
1929 	if (!fn.size())
1930 		return;
1931 	SourceEditor *sourceEditor = this->editorFor(fn, true);
1932 	if (!sourceEditor) {
1933 		sourceEditor = this->onFileNew();
1934 		sourceEditor->load(fn);
1935 	}
1936 }
1937 
onFileExit()1938 void MainWindow::onFileExit() {
1939 	assert(this_thread::get_id()==Console::guiThreadId);
1940 	if (this->close())
1941 		this->application->quit();
1942 }
1943 
onWindowClose()1944 void MainWindow::onWindowClose() {
1945 	assert(this_thread::get_id()==Console::guiThreadId);
1946 	QMdiSubWindow *w = this->mdiArea->activeSubWindow();
1947 	if (w)
1948 		w->close();
1949 }
1950 
onWindowCloseAll()1951 void MainWindow::onWindowCloseAll() {
1952 	assert(this_thread::get_id()==Console::guiThreadId);
1953 	this->mdiArea->closeAllSubWindows();
1954 }
1955 
onWindowTile()1956 void MainWindow::onWindowTile() {
1957 	assert(this_thread::get_id()==Console::guiThreadId);
1958 	this->mdiArea->tileSubWindows();
1959 }
1960 
onWindowCascade()1961 void MainWindow::onWindowCascade() {
1962 	assert(this_thread::get_id()==Console::guiThreadId);
1963 	this->console->lower();
1964 	this->mdiArea->cascadeSubWindows();
1965 }
1966 
onWindowNext()1967 void MainWindow::onWindowNext() {
1968 	assert(this_thread::get_id()==Console::guiThreadId);
1969 	this->mdiArea->activateNextSubWindow();
1970 }
1971 
onWindowPrevious()1972 void MainWindow::onWindowPrevious() {
1973 	assert(this_thread::get_id()==Console::guiThreadId);
1974 	this->mdiArea->activatePreviousSubWindow();
1975 }
1976 
onHelpAbout()1977 void MainWindow::onHelpAbout() {
1978 	assert(this_thread::get_id()==Console::guiThreadId);
1979 	QMessageBox::about(this, "About C5",
1980 			QString::fromStdString("<H1 align=center>C5</H1>"
1981 			"<H3 align=center>CoCoA 5 Integrated Development Environment</H3>"
1982 			"<p><p align=center>Alpha-version, for testing purposes only."
1983 			"<p align=center>Please, <font color=red><strong>DO NOT DISTRIBUTE</strong></font> this file."
1984 			"<p><p><p>For more information about CoCoA, please visit the official <a href=\"http://cocoa.dima.unige.it/\">website</a>"));
1985 }
1986 
1987 namespace {
loadSettings(QApplication & app)1988 	void loadSettings(QApplication &app) {
1989 		QSettings settings;
1990 		QVariant fontFamily, fontPointSize, fontWeight, fontItalic;
1991 		fontFamily = settings.value(SETTING_FONT_FAMILY);
1992 		if (!fontFamily.isNull()) {
1993 			fontPointSize = settings.value(SETTING_FONT_POINTSIZE);
1994 			fontWeight = settings.value(SETTING_FONT_WEIGHT);
1995 			fontItalic = settings.value(SETTING_FONT_ITALIC);
1996 			//cout << "setting font " << fontFamily.toString().toStdString() << ", point-size=" << fontPointSize.toInt()
1997 			//		<< ", weight=" << fontWeight.toInt() << ", italic=" << fontItalic.toBool() << endl;
1998 			app.setFont(QFont(fontFamily.toString(), fontPointSize.toInt(), fontWeight.toInt(), fontItalic.toBool()));
1999 		}
2000 	}
2001 }
2002 
launchTheIDE(WarningSeverity warningLevel,bool warnAboutCocoa5,const vector<string> & packageList,bool fullCoCoALibError)2003 int launchTheIDE(WarningSeverity warningLevel, bool warnAboutCocoa5, const vector<string> &packageList, bool fullCoCoALibError) {
2004 	QCoreApplication::setOrganizationName("CoCoA Team");
2005 	QCoreApplication::setOrganizationDomain("cocoa.dima.unige.it");
2006 	QCoreApplication::setApplicationName("C5");
2007 	char *argv[] = {const_cast<char *>("C5"), 0};
2008 	int argc = 1;
2009 	QApplication::setStyle(new QPlastiqueStyle);
2010 	QApplication app(argc, argv);
2011 	initQCodeEdit();
2012 	app.setWindowIcon(QIcon(":/images/CoCoALogo-icon.png"));
2013 	loadSettings(app);
2014 	QEditor::setDefaultFlags(QEditor::defaultFlags()|QEditor::LineWrap);
2015 	QDocument::setFont(app.font());
2016 	CoCoA::IDE::MainWindow main(0, &app, warningLevel, warnAboutCocoa5, packageList, fullCoCoALibError);
2017 	main.showNormal();
2018 	return app.exec();
2019 }
2020 
loadPackages(Console * console)2021 bool loadPackages(Console *console) {
2022 	return console->myLoadPackages();
2023 }
2024 
2025 } // namespace IDE
2026 
2027 } // namespace CoCoA
2028 
2029 #endif // #ifdef C5IDE
2030