/*
* Copyright (c) 2002-2006 Samit Basu
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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
*
*/
#include "Editor.hpp"
#include "Interpreter.hpp"
#include "highlighter.hpp"
#include <QtGui>
FMFindDialog::FMFindDialog(QWidget *parent) : QDialog(parent) {
ui.setupUi(this);
connect(ui.btFind,SIGNAL(clicked()),this,SLOT(find()));
connect(ui.btClose,SIGNAL(clicked()),this,SLOT(hide()));
setWindowIcon(QIcon(QString::fromUtf8(":/images/freemat_small_mod_64.png")));
setWindowTitle("Find - " + QString::fromStdString(Interpreter::getVersionString()));
ui.btFind->setIcon(QIcon(QString::fromUtf8(":/images/find.png")));
ui.btClose->setIcon(QIcon(QString::fromUtf8(":/images/close.png")));
}
void FMFindDialog::find() {
emit doFind(ui.cmFindText->currentText(), ui.cbBackwards->checkState() == Qt::Checked,
ui.cbSensitive->checkState() == Qt::Checked);
}
void FMFindDialog::found() {
ui.lbStatus->setText("");
}
void FMFindDialog::notfound() {
if (ui.cbBackwards->checkState())
ui.lbStatus->setText("Search reached start of document");
else
ui.lbStatus->setText("Search reached end of document");
}
FMReplaceDialog::FMReplaceDialog(QWidget *parent) : QDialog(parent) {
ui.setupUi(this);
connect(ui.btFind,SIGNAL(clicked()),this,SLOT(find()));
connect(ui.btClose,SIGNAL(clicked()),this,SLOT(hide()));
connect(ui.btReplace,SIGNAL(clicked()),this,SLOT(replace()));
connect(ui.btReplaceAll,SIGNAL(clicked()),this,SLOT(replaceAll()));
setWindowIcon(QIcon(QString::fromUtf8(":/images/freemat_small_mod_64.png")));
setWindowTitle("Find - " + QString::fromStdString(Interpreter::getVersionString()));
ui.btFind->setIcon(QIcon(QString::fromUtf8(":/images/find.png")));
ui.btClose->setIcon(QIcon(QString::fromUtf8(":/images/close.png")));
}
void FMReplaceDialog::find() {
emit doFind(ui.cmFindText->currentText(),
ui.cbBackwards->checkState() == Qt::Checked,
ui.cbSensitive->checkState() == Qt::Checked);
}
void FMReplaceDialog::replace() {
emit doReplace(ui.cmFindText->currentText(),
ui.cmReplaceText->currentText(),
ui.cbBackwards->checkState() == Qt::Checked,
ui.cbSensitive->checkState() == Qt::Checked);
}
void FMReplaceDialog::replaceAll() {
emit doReplaceAll(ui.cmFindText->currentText(),
ui.cmReplaceText->currentText(),
ui.cbBackwards->checkState() == Qt::Checked,
ui.cbSensitive->checkState() == Qt::Checked);
}
void FMReplaceDialog::found() {
ui.lbStatus->setText("");
}
void FMReplaceDialog::notfound() {
if (ui.cbBackwards->checkState())
ui.lbStatus->setText("Search reached start of document");
else
ui.lbStatus->setText("Search reached end of document");
}
void FMReplaceDialog::showrepcount(int cnt) {
QString p;
if (cnt == 0)
p = QString("Found no instances of the search text");
else if (cnt == 1)
p = QString("Replaced one occurance");
else
p = QString("Replaced %1 occurances").arg(cnt);
ui.lbStatus->setText(p);
}
FMTextEdit::FMTextEdit() : QTextEdit() {
QSettings settings("FreeMat","FreeMat");
indentSize = settings.value("editor/tab_size",3).toInt();
indentActive = settings.value("editor/indent_enable",true).toBool();
setLineWrapMode(QTextEdit::NoWrap);
}
FMTextEdit::~FMTextEdit() {
}
bool FMTextEdit::replace(QString text, QString reptext, QTextDocument::FindFlags flag) {
if (textCursor().selectedText() == text) {
QTextCursor cursor(textCursor());
cursor.insertText(reptext);
cursor.movePosition(QTextCursor::Left,QTextCursor::KeepAnchor,reptext.size());
setTextCursor(cursor);
return true;
}
if (find(text,flag)) {
QTextCursor cursor(textCursor());
cursor.insertText(reptext);
cursor.movePosition(QTextCursor::Left,QTextCursor::KeepAnchor,reptext.size());
setTextCursor(cursor);
return true;
} else
return false;
}
int FMTextEdit::replaceAll(QString text, QString reptext, QTextDocument::FindFlags flag) {
textCursor().beginEditBlock();
int repcount = 0;
while (replace(text,reptext,flag))
repcount++;
textCursor().endEditBlock();
return repcount;
}
void FMTextEdit::comment() {
QTextCursor cursor(textCursor());
QTextCursor line1(cursor);
QTextCursor line2(cursor);
if (cursor.position() < cursor.anchor()) {
line2.setPosition(cursor.anchor());
} else {
line1.setPosition(cursor.anchor());
}
line1.movePosition(QTextCursor::StartOfLine,QTextCursor::MoveAnchor);
line2.movePosition(QTextCursor::StartOfLine,QTextCursor::MoveAnchor);
QTextCursor pos(line1);
pos.beginEditBlock();
while (pos.position() <= line2.position()) {
pos.insertText("%");
pos.movePosition(QTextCursor::Down,QTextCursor::MoveAnchor);
}
pos.endEditBlock();
}
void FMTextEdit::uncomment() {
QTextCursor cursor(textCursor());
QTextCursor line1(cursor);
QTextCursor line2(cursor);
if (cursor.position() < cursor.anchor()) {
line2.setPosition(cursor.anchor());
} else {
line1.setPosition(cursor.anchor());
}
line1.movePosition(QTextCursor::StartOfLine,QTextCursor::MoveAnchor);
line2.movePosition(QTextCursor::StartOfLine,QTextCursor::MoveAnchor);
QTextCursor pos(line1);
pos.beginEditBlock();
while (pos.position() <= line2.position()) {
pos.movePosition(QTextCursor::Right,QTextCursor::KeepAnchor);
if (pos.selectedText() == "%")
pos.deleteChar();
pos.movePosition(QTextCursor::Down,QTextCursor::MoveAnchor);
pos.movePosition(QTextCursor::StartOfLine,QTextCursor::MoveAnchor);
}
pos.endEditBlock();
}
void FMTextEdit::keyPressEvent(QKeyEvent*e) {
bool tab = false;
int keycode = e->key();
bool delayedIndent = false;
if (keycode) {
QByteArray p(e->text().toAscii());
char key;
if (!e->text().isEmpty())
key = p[0];
else
key = 0;
if (key == 0x09) {
tab = true;
if (indentActive)
emit indent();
}
if (key == 13) {
delayedIndent = true;
if (indentActive)
emit indent();
}
}
if (!tab || !indentActive)
QTextEdit::keyPressEvent(e);
else
e->accept();
if (indentActive)
if (delayedIndent)
emit indent();
}
void FMTextEdit::contextMenuEvent(QContextMenuEvent* e) {
e->ignore();
}
void FMTextEdit::fontUpdate() {
QFontMetrics fm(font());
QFontInfo fi(font());
if (!fi.fixedPitch())
QMessageBox::warning(this,"FreeMat",
"You have selected a font that is not a fixed pitch.\nThe editor really requires a fixed pitch font to work.");
setTabStopWidth(fm.width(' ')*indentSize);
}
FMIndent::FMIndent() {
QSettings settings("FreeMat","FreeMat");
indentSize = settings.value("editor/tab_size",3).toInt();
}
FMIndent::~FMIndent() {
}
void FMIndent::setDocument(FMTextEdit *te) {
m_te = te;
}
FMTextEdit* FMIndent::document() const {
return m_te;
}
void removeMatch(QString &a, QRegExp &pattern) {
int k = 0;
while ((k = a.indexOf(pattern,k)) != -1) {
for ( int i = 0; i < pattern.matchedLength(); i++ )
a.replace(k+i,1,'X');
k += pattern.matchedLength();
}
}
int countMatches(QString a, QRegExp &pattern) {
int matchCount = 0;
int k = 0;
while ((k = a.indexOf(pattern,k)) != -1) {
matchCount++;
k += pattern.matchedLength();
}
return matchCount;
}
QString setIndentSpace(QString toIndent, int leading_space) {
QRegExp whitespace("^\\s*");
int k;
if ((k = toIndent.indexOf(whitespace,0)) != -1)
toIndent.remove(0,whitespace.matchedLength());
toIndent = QString(leading_space,' ') + toIndent;
return toIndent;
}
bool allWhiteSpace(QString a) {
QRegExp whitespace("^\\s*");
int k;
if ((k = a.indexOf(whitespace,0)) != -1)
a.remove(0,whitespace.matchedLength());
return (a.isEmpty());
}
QString stripLine(QString a) {
// 2. Replace string literals in <a> with dummy characters to avoid confusion,
// i.e., 'blahbla' --> xxxxxxxx
QRegExp literal("\'([^\']*)\'");
removeMatch(a,literal);
// 3. Replace 'end' keyword as used as a dimension token with dummy characters
// to avoid confusion,
// i.e., A(end,:) --> A(xxx,:)
// We do this by replacing \([^\)]*(\bend\b)[^\)]*\) --> xxx
QRegExp endparen("\\([^\\)]*(\\bend\\b)[^\\)]*\\)");
removeMatch(a,endparen);
QRegExp endbracket("\\{[^\\}]*(\\bend\\b)[^\\}]*\\}");
removeMatch(a,endbracket);
// 4. Strip comments
// i.e., % foo --> %
QRegExp comment("%.*");
removeMatch(a,comment);
return a;
}
int computeIndexIncrement(QString a) {
// 5. Compute the incremental index level - this is the number of
// matches to: if, else, elseif, for, function, try, catch, while, switch
// minus the number of matches to 'end'.
QRegExp keyword_in("\\b(if|for|function|try|while|switch)\\b");
QRegExp keyword_out("\\bend\\b");
int indent_in_count = countMatches(a,keyword_in);
int indent_out_count = countMatches(a,keyword_out);
int indent_increment = indent_in_count - indent_out_count;
return indent_increment;
}
QString indentLine(QString toIndent, QStringList priorText, int indentSize) {
// Two observations:
// 1. If the _current_ line contains contains 'end' in excess of 'for', etc., then
// the indentation for the current line should be 1 less.
// 2. The cursor should not move relative to the text when things are tabbed
QString a;
// 1. Let <a> be the line of non-blank text prior to the current one. If
// no such line exists, then we are done (no change).
while (!priorText.empty() && allWhiteSpace(priorText.last()))
priorText.removeLast();
if (priorText.empty())
return setIndentSpace(toIndent,0);
a = priorText.last();
// Strip the prior line of confusing constructs...
a = stripLine(a);
int indent_increment = computeIndexIncrement(a);
QString b = stripLine(toIndent);
// Some lines require an adjustment: "catch,elseif,else,end"
QRegExp keyword_adjust("^\\s*\\b(end|else|elseif|catch)\\b");
if (b.indexOf(keyword_adjust) >= 0) {
indent_increment--;
}
if (a.indexOf(keyword_adjust) >= 0) {
indent_increment++;
}
QRegExp function_det("^\\s*\\b(function)\\b");
if (b.indexOf(function_det) >= 0) {
return setIndentSpace(toIndent,0);
}
// 6. Find the start of non-white text in <a>, add incremental index*tab size - this
// is the amount of white space at the beginning of the current line.
QRegExp whitespace("^\\s*");
int leading_space = 0;
int k = 0;
if ((k = a.indexOf(whitespace,0)) != -1)
leading_space = whitespace.matchedLength();
leading_space += indent_increment*indentSize;
leading_space = qMax(leading_space,0);
// Indenting is simpler for us than for QSA or for C++. We simply
// apply the following rules:
// 7. Adjust the current line.
// if ((b.indexOf(else_only) >= 0))
// indent_increment++;
return setIndentSpace(toIndent,leading_space);
}
void FMIndent::update() {
QTextCursor cursor(m_te->textCursor());
QTextCursor save(cursor);
QTextCursor final(cursor);
// Get the current cursor position relative to the start of the line
final.movePosition(QTextCursor::StartOfLine,QTextCursor::KeepAnchor);
int curpos = final.selectedText().length();
cursor.movePosition(QTextCursor::StartOfLine);
cursor.movePosition(QTextCursor::EndOfLine,QTextCursor::KeepAnchor);
QString toIndent(cursor.selectedText());
cursor.movePosition(QTextCursor::StartOfLine);
cursor.movePosition(QTextCursor::Start,QTextCursor::KeepAnchor);
QStringList priorlines(cursor.selection().toPlainText().split("\n"));
QString indented(indentLine(toIndent,priorlines,indentSize));
save.movePosition(QTextCursor::StartOfLine);
save.movePosition(QTextCursor::EndOfLine,QTextCursor::KeepAnchor);
save.insertText(indented);
// Move the cursor to where it was relative to the original text
// The number of characters inserted
int orig_length = toIndent.length();
int new_length = indented.length();
int new_pos = qMax(curpos + new_length-orig_length,0);
final.movePosition(QTextCursor::StartOfLine,QTextCursor::MoveAnchor);
final.movePosition(QTextCursor::Right,QTextCursor::MoveAnchor,new_pos);
m_te->setTextCursor(final);
}
Interpreter* FMEditPane::getInterpreter() {
return m_eval;
}
FMEditPane::FMEditPane(Interpreter* eval) : QWidget() {
m_eval = eval;
tEditor = new FMTextEdit;
LineNumber *tLN = new LineNumber(tEditor);
BreakPointIndicator *tBP = new BreakPointIndicator(tEditor,this);
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(tLN);
layout->addWidget(tBP);
layout->addWidget(tEditor);
setLayout(layout);
FMIndent *ind = new FMIndent;
connect(tEditor,SIGNAL(indent()),ind,SLOT(update()));
Highlighter *highlight = new Highlighter(tEditor->document());
ind->setDocument(tEditor);
}
FMTextEdit* FMEditPane::getEditor() {
return tEditor;
}
void FMEditPane::setFileName(QString filename) {
curFile = filename;
}
QString FMEditPane::getFileName() {
return curFile;
}
int FMEditPane::getLineNumber() {
QTextCursor tc(tEditor->textCursor());
tc.movePosition(QTextCursor::StartOfLine);
int linenumber = 1;
while (!tc.atStart()) {
tc.movePosition(QTextCursor::Up);
linenumber++;
}
return linenumber;
}
FMEditor::FMEditor(Interpreter* eval) : QMainWindow() {
m_eval = eval;
setWindowIcon(QPixmap(":/images/freemat_small_mod_64.png"));
prevEdit = NULL;
tab = new QTabWidget(this);
setCentralWidget(tab);
createActions();
createMenus();
createToolBars();
createStatusBar();
readSettings();
connect(tab,SIGNAL(currentChanged(int)),this,SLOT(tabChanged(int)));
addTab();
tabChanged(0);
m_find = new FMFindDialog;
connect(m_find,SIGNAL(doFind(QString,bool,bool)),
this,SLOT(doFind(QString,bool,bool)));
m_replace = new FMReplaceDialog;
connect(m_replace,SIGNAL(doFind(QString,bool,bool)),
this,SLOT(doFind(QString,bool,bool)));
connect(m_replace,SIGNAL(doReplace(QString,QString,bool,bool)),
this,SLOT(doReplace(QString,QString,bool,bool)));
connect(m_replace,SIGNAL(doReplaceAll(QString,QString,bool,bool)),
this,SLOT(doReplaceAll(QString,QString,bool,bool)));
}
void FMEditor::doFind(QString text, bool backwards, bool sensitive) {
QTextDocument::FindFlags flags;
if (backwards) flags = QTextDocument::FindBackward;
if (sensitive) flags = flags | QTextDocument::FindCaseSensitively;
if (!currentEditor()->find(text,flags)) {
m_find->notfound();
m_replace->notfound();
} else {
m_find->found();
m_replace->found();
}
}
void FMEditor::doReplace(QString text, QString replace,
bool backwards, bool sensitive) {
QTextDocument::FindFlags flags;
if (backwards) flags = QTextDocument::FindBackward;
if (sensitive) flags = flags | QTextDocument::FindCaseSensitively;
if (!currentEditor()->replace(text,replace,flags))
m_replace->notfound();
else
m_replace->found();
}
void FMEditor::doReplaceAll(QString text, QString reptxt,
bool backwards, bool sensitive) {
QTextDocument::FindFlags flags;
if (backwards) flags = QTextDocument::FindBackward;
if (sensitive) flags = flags | QTextDocument::FindCaseSensitively;
int repcount = currentEditor()->replaceAll(text,reptxt,flags);
m_replace->showrepcount(repcount);
}
void FMEditor::readSettings() {
QSettings settings("FreeMat", "FreeMat");
QPoint pos = settings.value("editor/pos", QPoint(200, 200)).toPoint();
QSize size = settings.value("editor/size", QSize(400, 400)).toSize();
resize(size);
move(pos);
QString font = settings.value("editor/font").toString();
if (!font.isNull()) {
QFont new_font;
new_font.fromString(font);
m_font = new_font;
}
updateFont();
}
void FMEditor::updateFont() {
for (int i=0;i<tab->count();i++) {
QWidget *p = tab->widget(i);
FMEditPane *te = qobject_cast<FMEditPane*>(p);
te->setFont(m_font);
te->getEditor()->fontUpdate();
}
}
void FMEditor::writeSettings() {
QSettings settings("FreeMat", "FreeMat");
settings.setValue("editor/pos", pos());
settings.setValue("editor/size", size());
settings.setValue("editor/font", m_font.toString());
settings.sync();
}
QString FMEditor::currentFilename() {
QWidget *p = tab->currentWidget();
FMEditPane *te = qobject_cast<FMEditPane*>(p);
if (!te) {
addTab();
p = tab->currentWidget();
te = qobject_cast<FMEditPane*>(p);
}
return te->getFileName();
}
void FMEditor::setCurrentFilename(QString filename) {
QWidget *p = tab->currentWidget();
FMEditPane *te = qobject_cast<FMEditPane*>(p);
if (!te) {
addTab();
p = tab->currentWidget();
te = qobject_cast<FMEditPane*>(p);
}
te->setFileName(filename);
}
FMTextEdit* FMEditor::currentEditor() {
QWidget *p = tab->currentWidget();
FMEditPane *te = qobject_cast<FMEditPane*>(p);
if (!te) {
addTab();
p = tab->currentWidget();
te = qobject_cast<FMEditPane*>(p);
}
return te->getEditor();
}
void FMEditor::addTab() {
tab->addTab(new FMEditPane(m_eval),"untitled.m");
tab->setCurrentIndex(tab->count()-1);
updateFont();
}
FMEditor::~FMEditor() {
}
QString FMEditor::shownName() {
QString sName;
if (currentFilename().isEmpty())
sName = "untitled.m";
else
sName = strippedName(currentFilename());
return sName;
}
void FMEditor::updateTitles() {
tab->setTabText(tab->currentIndex(),shownName());
setWindowTitle(QString("%1[*]").arg(shownName()) + " - " + QString::fromStdString(Interpreter::getVersionString()) + " Editor");
documentWasModified();
}
void FMEditor::tabChanged(int newslot) {
disconnect(cutAct,SIGNAL(triggered()),0,0);
disconnect(copyAct,SIGNAL(triggered()),0,0);
disconnect(pasteAct,SIGNAL(triggered()),0,0);
connect(cutAct,SIGNAL(triggered()),currentEditor(),SLOT(cut()));
connect(copyAct,SIGNAL(triggered()),currentEditor(),SLOT(copy()));
connect(pasteAct,SIGNAL(triggered()),currentEditor(),SLOT(paste()));
// Disconnect each of the contents changed signals
if (prevEdit)
disconnect(prevEdit->document(),SIGNAL(contentsChanged()),0,0);
// NEED TO DISCONNECT...
connect(currentEditor()->document(),SIGNAL(contentsChanged()),this,SLOT(documentWasModified()));
updateTitles();
prevEdit = currentEditor();
}
void FMEditor::documentWasModified() {
setWindowModified(currentEditor()->document()->isModified());
if (currentEditor()->document()->isModified())
tab->setTabText(tab->currentIndex(),shownName()+"*");
else
tab->setTabText(tab->currentIndex(),shownName());
}
void FMEditor::createActions() {
newAct = new QAction(QIcon(":/images/new.png"),"&New Tab",this);
newAct->setShortcut(Qt::Key_N | Qt::CTRL);
connect(newAct,SIGNAL(triggered()),this,SLOT(addTab()));
openAct = new QAction(QIcon(":/images/open.png"),"&Open",this);
openAct->setShortcut(Qt::Key_O | Qt::CTRL);
connect(openAct,SIGNAL(triggered()),this,SLOT(open()));
saveAct = new QAction(QIcon(":/images/save.png"),"&Save",this);
saveAct->setShortcut(Qt::Key_S | Qt::CTRL);
connect(saveAct,SIGNAL(triggered()),this,SLOT(save()));
saveAsAct = new QAction("Save &As",this);
connect(saveAsAct,SIGNAL(triggered()),this,SLOT(saveAs()));
quitAct = new QAction(QIcon(":/images/quit.png"),"&Quit Editor",this);
quitAct->setShortcut(Qt::Key_Q | Qt::CTRL);
connect(quitAct,SIGNAL(triggered()),this,SLOT(close()));
closeAct = new QAction(QIcon(":/images/close.png"),"&Close Tab",this);
connect(closeAct,SIGNAL(triggered()),this,SLOT(closeTab()));
copyAct = new QAction(QIcon(":/images/copy.png"),"&Copy",this);
copyAct->setShortcut(Qt::Key_C | Qt::CTRL);
cutAct = new QAction(QIcon(":/images/cut.png"),"Cu&t",this);
cutAct->setShortcut(Qt::Key_X | Qt::CTRL);
pasteAct = new QAction(QIcon(":/images/paste.png"),"&Paste",this);
pasteAct->setShortcut(Qt::Key_V | Qt::CTRL);
fontAct = new QAction("&Font",this);
connect(fontAct,SIGNAL(triggered()),this,SLOT(font()));
findAct = new QAction(QIcon(":/images/find.png"),"&Find",this);
findAct->setShortcut(Qt::Key_F | Qt::CTRL);
connect(findAct,SIGNAL(triggered()),this,SLOT(find()));
commentAct = new QAction("Comment Region",this);
connect(commentAct,SIGNAL(triggered()),this,SLOT(comment()));
uncommentAct = new QAction("Uncomment Region",this);
connect(uncommentAct,SIGNAL(triggered()),this,SLOT(uncomment()));
undoAct = new QAction("Undo Edits",this);
undoAct->setShortcut(Qt::Key_Z | Qt::CTRL);
connect(undoAct,SIGNAL(triggered()),this,SLOT(undo()));
redoAct = new QAction("Redo Edits",this);
redoAct->setShortcut(Qt::Key_Y | Qt::CTRL);
connect(redoAct,SIGNAL(triggered()),this,SLOT(redo()));
replaceAct = new QAction("Find and Replace",this);
connect(replaceAct,SIGNAL(triggered()),this,SLOT(replace()));
dbStepAct = new QAction(QIcon(":/images/dbgnext.png"),"&Step Over",this);
connect(dbStepAct,SIGNAL(triggered()),this,SLOT(dbstep()));
dbTraceAct = new QAction(QIcon(":/images/dbgstep.png"),"&Step Into",this);
connect(dbTraceAct,SIGNAL(triggered()),this,SLOT(dbtrace()));
dbContinueAct = new QAction(QIcon(":/images/dbgrun.png"),"&Continue",this);
connect(dbContinueAct,SIGNAL(triggered()),this,SLOT(dbcontinue()));
dbSetClearBPAct = new QAction(QIcon(":/images/stop.png"),"Set/Clear Breakpoint",this);
connect(dbSetClearBPAct,SIGNAL(triggered()),this,SLOT(dbsetclearbp()));
dbStopAct = new QAction(QIcon(":/images/player_stop.png"),"Stop Debugging",this);
connect(dbStopAct,SIGNAL(triggered()),this,SLOT(dbstop()));
colorConfigAct = new QAction("Text Highlighting",this);
connect(colorConfigAct,SIGNAL(triggered()),this,SLOT(configcolors()));
indentConfigAct = new QAction("Indenting",this);
connect(indentConfigAct,SIGNAL(triggered()),this,SLOT(configindent()));
executeSelectedAct = new QAction("Execute Selected Text",this);
connect(executeSelectedAct,SIGNAL(triggered()),this,SLOT(execSelected()));
executeCurrentAct = new QAction("Execute Current Buffer",this);
connect(executeCurrentAct,SIGNAL(triggered()),this,SLOT(execCurrent()));
}
void FMEditor::execSelected() {
emit EvaluateText(currentEditor()->textCursor().selectedText());
}
void FMEditor::execCurrent() {
if (currentFilename().isEmpty())
QMessageBox::information(this,"Cannot execute unnamed buffer","You must save the current buffer into a file before it can be executed.");
else {
if (!maybeSave())
return;
emit EvaluateText("source " + currentFilename() + "\n");
}
}
void FMEditor::undo() {
currentEditor()->undo();
}
void FMEditor::redo() {
currentEditor()->redo();
}
void FMEditor::comment() {
currentEditor()->comment();
}
void FMEditor::uncomment() {
currentEditor()->uncomment();
}
void FMEditor::find() {
m_find->show();
m_find->raise();
}
void FMEditor::replace() {
m_replace->show();
m_replace->raise();
}
void FMEditor::createMenus() {
fileMenu = menuBar()->addMenu("&File");
fileMenu->addAction(newAct);
fileMenu->addAction(openAct);
fileMenu->addAction(saveAct);
fileMenu->addAction(saveAsAct);
fileMenu->addAction(closeAct);
fileMenu->addAction(quitAct);
editMenu = menuBar()->addMenu("&Edit");
editMenu->addAction(undoAct);
editMenu->addAction(redoAct);
editMenu->addAction(copyAct);
editMenu->addAction(cutAct);
editMenu->addAction(pasteAct);
QMenu* configMenu = editMenu->addMenu("&Preferences");
configMenu->addAction(fontAct);
configMenu->addAction(colorConfigAct);
configMenu->addAction(indentConfigAct);
toolsMenu = menuBar()->addMenu("&Tools");
toolsMenu->addAction(findAct);
toolsMenu->addAction(replaceAct);
toolsMenu->addAction(commentAct);
toolsMenu->addAction(uncommentAct);
debugMenu = menuBar()->addMenu("&Debug");
debugMenu->addAction(executeCurrentAct);
debugMenu->addAction(executeSelectedAct);
debugMenu->addSeparator();
debugMenu->addAction(dbStepAct);
debugMenu->addAction(dbTraceAct);
debugMenu->addAction(dbContinueAct);
debugMenu->addAction(dbSetClearBPAct);
debugMenu->addAction(dbStopAct);
m_popup = new QMenu;
m_popup->addAction(copyAct);
m_popup->addAction(cutAct);
m_popup->addAction(pasteAct);
m_popup->addSeparator();
m_popup->addAction(undoAct);
m_popup->addAction(redoAct);
m_popup->addSeparator();
m_popup->addAction(findAct);
m_popup->addAction(replaceAct);
m_popup->addSeparator();
m_popup->addAction(commentAct);
m_popup->addAction(uncommentAct);
m_popup->addSeparator();
m_popup->addAction(executeCurrentAct);
m_popup->addAction(executeSelectedAct);
m_popup->addSeparator();
m_popup->addAction(dbStepAct);
m_popup->addAction(dbTraceAct);
m_popup->addAction(dbContinueAct);
m_popup->addAction(dbSetClearBPAct);
m_popup->addAction(dbStopAct);
}
void FMEditor::contextMenuEvent(QContextMenuEvent *e) {
m_popup->exec(e->globalPos());
}
void FMEditor::createToolBars() {
fileToolBar = addToolBar("File");
fileToolBar->addAction(newAct);
fileToolBar->addAction(openAct);
fileToolBar->addAction(saveAct);
fileToolBar->addAction(closeAct);
editToolBar = addToolBar("Edit");
editToolBar->addAction(copyAct);
editToolBar->addAction(cutAct);
editToolBar->addAction(pasteAct);
debugToolBar = addToolBar("Debug");
debugToolBar->addAction(dbStepAct);
debugToolBar->addAction(dbTraceAct);
debugToolBar->addAction(dbContinueAct);
debugToolBar->addAction(dbSetClearBPAct);
debugToolBar->addAction(dbStopAct);
}
void FMEditor::configcolors() {
FMSynLightConf t(this);
t.exec();
}
void FMEditor::configindent() {
FMIndentConf t(this);
t.exec();
}
void FMEditor::dbstep() {
m_eval->ExecuteLine("dbstep\n");
}
void FMEditor::dbtrace() {
m_eval->ExecuteLine("dbtrace\n");
}
void FMEditor::dbcontinue() {
m_eval->ExecuteLine("return\n");
}
void FMEditor::dbsetclearbp() {
QWidget *p = tab->currentWidget();
FMEditPane *te = qobject_cast<FMEditPane*>(p);
// // DEMO
// QList<QTextEdit::ExtraSelection> selections;
// QTextCursor cursor(te->getEditor()->textCursor());
// cursor.movePosition(QTextCursor::StartOfLine,QTextCursor::MoveAnchor);
// cursor.movePosition(QTextCursor::Down,QTextCursor::KeepAnchor);
// QTextCharFormat format(te->getEditor()->currentCharFormat());
// format.setBackground(QBrush(Qt::red));
// QTextEdit::ExtraSelection sel;
// sel.format = format;
// sel.cursor = cursor;
// selections.push_back(sel);
// te->getEditor()->dsetExtraSelections(selections);
m_eval->toggleBP(te->getFileName(),te->getLineNumber());
}
void FMEditor::dbstop() {
m_eval->ExecuteLine("retall\n");
}
void FMEditor::createStatusBar() {
statusBar()->showMessage("Ready");
}
static QString lastfile;
static bool lastfile_set = false;
static QString GetOpenFileName(QWidget *w) {
QString retfile;
if (lastfile_set)
retfile = QFileDialog::getOpenFileName(w,"Open File in Editor",lastfile,
"M files (*.m);;Text files (*.txt);;All files (*)");
else
retfile = QFileDialog::getOpenFileName(w,"Open File in Editor",QString(),
"M files (*.m);;Text files (*.txt);;All files (*)");
if (!retfile.isEmpty()) {
QFileInfo tokeep(retfile);
lastfile = tokeep.absolutePath();
lastfile_set = true;
}
return retfile;
}
static QString GetSaveFileName(QWidget *w) {
QString retfile;
if (lastfile_set)
retfile = QFileDialog::getSaveFileName(w,"Save File",lastfile,
"M files (*.m);;Text files (*.txt);;All files (*)");
else
retfile = QFileDialog::getSaveFileName(w,"Save File",QString(),
"M files (*.m);;Text files (*.txt);;All files (*)");
if (!retfile.isEmpty()) {
QFileInfo tokeep(retfile);
lastfile = tokeep.absolutePath();
lastfile_set = true;
}
return retfile;
}
void FMEditor::open() {
if (currentEditor()->document()->isModified() ||
(tab->tabText(tab->currentIndex()) != "untitled.m")) {
tab->addTab(new FMEditPane(m_eval),"untitled.m");
tab->setCurrentIndex(tab->count()-1);
updateFont();
}
QString fileName = GetOpenFileName(this);
if (!fileName.isEmpty()) {
loadFile(fileName);
}
}
bool FMEditor::save() {
if (currentFilename().isEmpty()) {
return saveAs();
} else {
return saveFile(currentFilename());
}
}
bool FMEditor::saveAs() {
QString fileName = GetSaveFileName(this);
if (fileName.isEmpty())
return false;
// Check for a conflict
for (int i=0;i<tab->count();i++) {
QWidget *w = tab->widget(i);
FMEditPane *te = qobject_cast<FMEditPane*>(w);
if (te) {
if ((te->getFileName() == fileName)
&& (i != tab->currentIndex())) {
QMessageBox::critical(this,"FreeMat","Cannot save to filename\n " + fileName + "\n as this file is open in another tab.\n Please close the other tab and\n then repeat the save operation.");
tab->setCurrentIndex(i);
return false;
}
}
}
return saveFile(fileName);
}
void FMEditor::RefreshBPLists() {
update();
}
void FMEditor::ShowActiveLine() {
// Find the tab with this matching filename
QString tname(QString::fromStdString(m_eval->getInstructionPointerFileName()));
if (tname == "") return;
// Check for one of the editors that might be editing this file already
for (int i=0;i<tab->count();i++) {
QWidget *w = tab->widget(i);
FMEditPane *te = qobject_cast<FMEditPane*>(w);
if (te) {
if (te->getFileName() == tname) {
tab->setCurrentIndex(i);
update();
return;
}
}
}
if (currentEditor()->document()->isModified() ||
(tab->tabText(tab->currentIndex()) != "untitled.m")) {
tab->addTab(new FMEditPane(m_eval),"untitled.m");
tab->setCurrentIndex(tab->count()-1);
updateFont();
}
loadFile(tname);
update();
}
void FMEditor::closeTab() {
if (maybeSave()) {
QWidget *p = tab->currentWidget();
tab->removeTab(tab->currentIndex());
prevEdit = NULL;
delete p;
}
}
bool FMEditor::maybeSave() {
if (currentEditor()->document()->isModified()) {
int ret = QMessageBox::warning(this, tr("FreeMat"),
"The document " + shownName() + " has been modified.\n"
"Do you want to save your changes?",
QMessageBox::Yes | QMessageBox::Default,
QMessageBox::No,
QMessageBox::Cancel | QMessageBox::Escape);
if (ret == QMessageBox::Yes)
return save();
else if (ret == QMessageBox::Cancel)
return false;
}
return true;
}
bool FMEditor::saveFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QFile::WriteOnly | QFile::Text)) {
QMessageBox::warning(this, tr("FreeMat"),
tr("Cannot write file %1:\n%2.")
.arg(fileName)
.arg(file.errorString()));
return false;
}
QTextStream out(&file);
QApplication::setOverrideCursor(Qt::WaitCursor);
out << currentEditor()->toPlainText();
QApplication::restoreOverrideCursor();
setCurrentFile(fileName);
statusBar()->showMessage(tr("File saved"), 2000);
return true;
}
void FMEditor::closeEvent(QCloseEvent *event) {
while (tab->count() > 0) {
if (!maybeSave()) {
event->ignore();
return;
} else {
QWidget *p = tab->currentWidget();
tab->removeTab(tab->currentIndex());
prevEdit = NULL;
delete p;
}
}
writeSettings();
event->accept();
}
void FMEditor::loadFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
QMessageBox::warning(this, tr("FreeMat"),
tr("Cannot read file %1:\n%2.")
.arg(fileName)
.arg(file.errorString()));
return;
}
// Check for one of the editors that might be editing this file already
for (int i=0;i<tab->count();i++) {
QWidget *w = tab->widget(i);
FMEditPane *te = qobject_cast<FMEditPane*>(w);
if (te) {
if (te->getFileName() == fileName) {
tab->setCurrentIndex(i);
return;
}
}
}
QTextStream in(&file);
QApplication::setOverrideCursor(Qt::WaitCursor);
currentEditor()->setPlainText(in.readAll());
QApplication::restoreOverrideCursor();
setCurrentFile(fileName);
statusBar()->showMessage(tr("File loaded"), 2000);
}
void FMEditor::setCurrentFile(const QString &fileName)
{
setCurrentFilename(fileName);
currentEditor()->document()->setModified(false);
setWindowModified(false);
updateTitles();
}
QString FMEditor::strippedName(const QString &fullFileName)
{
return QFileInfo(fullFileName).fileName();
}
void FMEditor::font() {
bool ok;
QFont new_font = QFontDialog::getFont(&ok, m_font, this);
m_font = new_font;
updateFont();
}
BreakPointIndicator::BreakPointIndicator(FMTextEdit *editor, FMEditPane* pane) :
QWidget(), tEditor(editor), tPane(pane) {
setFixedWidth(fontMetrics().width(QLatin1String("0000")+5));
connect(tEditor->document()->documentLayout(),
SIGNAL(update(const QRectF &)),
this, SLOT(update()));
connect(tEditor->verticalScrollBar(), SIGNAL(valueChanged(int)),
this, SLOT(update()));
}
void BreakPointIndicator::mousePressEvent(QMouseEvent *e) {
int contentsY = tEditor->verticalScrollBar()->value();
qreal pageBottom = contentsY + tEditor->viewport()->height();
int lineNumber = 1;
QString fname(tPane->getFileName());
Interpreter *eval(tPane->getInterpreter());
for (QTextBlock block = tEditor->document()->begin();
block.isValid(); block = block.next(), ++lineNumber) {
QTextLayout *layout = block.layout();
const QRectF boundingRect = layout->boundingRect();
QPointF position = layout->position();
if (position.y() + boundingRect.height() < contentsY)
continue;
if (position.y() > pageBottom)
break;
if ((e->y() >= (2+qRound(position.y()) - contentsY)) &&
(e->y() < (2+qRound(position.y()) - contentsY) + width()/2)) {
eval->toggleBP(fname,lineNumber);
}
}
}
void BreakPointIndicator::paintEvent(QPaintEvent *) {
int contentsY = tEditor->verticalScrollBar()->value();
qreal pageBottom = contentsY + tEditor->viewport()->height();
int lineNumber = 1;
QPainter p(this);
// Get the list of breakpoints
QString fname(tPane->getFileName());
Interpreter *eval(tPane->getInterpreter());
int w = width()/2;
for (QTextBlock block = tEditor->document()->begin();
block.isValid(); block = block.next(), ++lineNumber) {
QTextLayout *layout = block.layout();
const QRectF boundingRect = layout->boundingRect();
QPointF position = layout->position();
if (position.y() + boundingRect.height() < contentsY)
continue;
if (position.y() > pageBottom)
break;
if (eval->isBPSet(fname,lineNumber))
p.drawPixmap(2, 2+qRound(position.y()) - contentsY,
w-4,w-4,QPixmap(":/images/stop.png"),
0,0,32,32);
if (eval->isInstructionPointer(fname,lineNumber))
p.drawPixmap(w, 2+qRound(position.y()) - contentsY,
w-4,w-4,QPixmap(":/images/player_play.png"),
0,0,32,32);
}
}
LineNumber::LineNumber(FMTextEdit *editor) : QWidget(), tEditor(editor) {
setFixedWidth(fontMetrics().width(QLatin1String("0000")+5));
connect(tEditor->document()->documentLayout(),
SIGNAL(update(const QRectF &)),
this, SLOT(update()));
connect(tEditor->verticalScrollBar(), SIGNAL(valueChanged(int)),
this, SLOT(update()));
}
void LineNumber::paintEvent(QPaintEvent *)
{
int contentsY = tEditor->verticalScrollBar()->value();
qreal pageBottom = contentsY + tEditor->viewport()->height();
int lineNumber = 1;
setFont(tEditor->font());
const QFontMetrics fm = fontMetrics();
const int ascent = fontMetrics().ascent() + 1; // height = ascent + descent + 1
QPainter p(this);
for (QTextBlock block = tEditor->document()->begin();
block.isValid(); block = block.next(), ++lineNumber) {
QTextLayout *layout = block.layout();
const QRectF boundingRect = layout->boundingRect();
QPointF position = layout->position();
if (position.y() + boundingRect.height() < contentsY)
continue;
if (position.y() > pageBottom)
break;
const QString txt = QString::number(lineNumber);
p.drawText(width() - fm.width(txt), qRound(position.y()) - contentsY + ascent, txt);
}
}
FMSynLightConf::FMSynLightConf(QWidget *parent) : QDialog(parent) {
ui.setupUi(this);
QSettings settings("FreeMat","FreeMat");
connect(ui.enableCB,SIGNAL(toggled(bool)),ui.keywordButton,SLOT(setEnabled(bool)));
connect(ui.enableCB,SIGNAL(toggled(bool)),ui.commentsButton,SLOT(setEnabled(bool)));
connect(ui.enableCB,SIGNAL(toggled(bool)),ui.stringsButton,SLOT(setEnabled(bool)));
connect(ui.enableCB,SIGNAL(toggled(bool)),ui.untermStringButton,SLOT(setEnabled(bool)));
ui.enableCB->setChecked(settings.value("editor/syntax_enable",true).toBool());
setLabelColor(ui.keywordLabel,
settings.value("editor/syntax_colors/keyword",Qt::darkBlue).value<QColor>());
setLabelColor(ui.commentsLabel,
settings.value("editor/syntax_colors/comments",Qt::darkRed).value<QColor>());
setLabelColor(ui.stringsLabel,
settings.value("editor/syntax_colors/strings",Qt::darkGreen).value<QColor>());
setLabelColor(ui.untermStringsLabel,
settings.value("editor/syntax_colors/untermstrings",Qt::darkRed).value<QColor>());
connect(ui.keywordButton,SIGNAL(clicked()),this,SLOT(setKeywordColor()));
connect(ui.commentsButton,SIGNAL(clicked()),this,SLOT(setCommentColor()));
connect(ui.stringsButton,SIGNAL(clicked()),this,SLOT(setStringColor()));
connect(ui.untermStringButton,SIGNAL(clicked()),this,SLOT(setUntermStringColor()));
connect(this,SIGNAL(accepted()),this,SLOT(save()));
}
void FMSynLightConf::save() {
QSettings settings("FreeMat", "FreeMat");
settings.setValue("editor/syntax_enable",ui.enableCB->isChecked());
settings.setValue("editor/syntax_colors/keyword",getColor(ui.keywordLabel));
settings.setValue("editor/syntax_colors/comments",getColor(ui.commentsLabel));
settings.setValue("editor/syntax_colors/strings",getColor(ui.stringsLabel));
settings.setValue("editor/syntax_colors/untermstrings",getColor(ui.untermStringsLabel));
QMessageBox::information(this,"Updated settings","The settings will apply to new files you open in the editor.");
}
QColor FMSynLightConf::getColor(QLabel *l) {
return (l->palette().color(QPalette::WindowText));
}
void FMSynLightConf::setLabelColor(QLabel *l, QColor c) {
if (c.isValid()) {
QPalette palette(l->palette());
palette.setColor(QPalette::WindowText, c);
l->setPalette(palette);
}
}
void FMSynLightConf::querySetLabelColor(QLabel *l) {
setLabelColor(l,QColorDialog::getColor(l->palette().color(QPalette::WindowText), this));
}
void FMSynLightConf::setKeywordColor() {
querySetLabelColor(ui.keywordLabel);
}
void FMSynLightConf::setCommentColor() {
querySetLabelColor(ui.commentsLabel);
}
void FMSynLightConf::setStringColor() {
querySetLabelColor(ui.stringsLabel);
}
void FMSynLightConf::setUntermStringColor() {
querySetLabelColor(ui.untermStringsLabel);
}
FMIndentConf::FMIndentConf(QWidget *parent) : QDialog(parent) {
ui.setupUi(this);
QSettings settings("FreeMat","FreeMat");
ui.indentEnable->setChecked(settings.value("editor/indent_enable",true).toBool());
ui.tabsize->setText(settings.value("editor/tab_size",3).toString());
connect(this,SIGNAL(accepted()),this,SLOT(save()));
}
void FMIndentConf::save() {
QSettings settings("FreeMat","FreeMat");
settings.setValue("editor/indent_enable",ui.indentEnable->isChecked());
settings.setValue("editor/tab_size",ui.tabsize->text().toInt());
QMessageBox::information(this,"Updated settings","The settings will apply to new files you open in the editor.");
}
syntax highlighted by Code2HTML, v. 0.9.1