You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1162 lines
32 KiB
1162 lines
32 KiB
/* |
|
XINX |
|
Copyright (C) 2007-2011 by Ulrich Van Den Hekke |
|
xinx@shadoware.org |
|
|
|
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 3 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, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
// Xinx header |
|
#include "editors/xinxcodeedit_p.h" |
|
#include "core/xinxconfig.h" |
|
#include "editors/xinxlanguagefactory.h" |
|
#include "editors/xinxformatscheme.h" |
|
#include "snipets/snipetmanager.h" |
|
#include <contentview3/node.h> |
|
#include <codecompletion/model.h> |
|
#include <codecompletion/completer.h> |
|
#include <editors/textfileeditor.h> |
|
|
|
// Qt header |
|
#include <QHBoxLayout> |
|
#include <QAction> |
|
#include <QApplication> |
|
#include <QMimeData> |
|
#include <QClipboard> |
|
#include <QCompleter> |
|
#include <QMouseEvent> |
|
#include <QAbstractItemView> |
|
#include <QDebug> |
|
|
|
// QCodeEdit header |
|
#include <qpanellayout.h> |
|
#include <qdocument.h> |
|
#include <qdocumentsearch.h> |
|
#include <qdocumentline.h> |
|
#include <qlinemarkpanel.h> |
|
#include <qlinenumberpanel.h> |
|
#include <qfoldpanel.h> |
|
#include <qlinechangepanel.h> |
|
#include <qdocument_p.h> |
|
#include <qlanguagedefinition.h> |
|
#include <qlinemarksinfocenter.h> |
|
|
|
/*! |
|
* Definition of the characters that can't be in a word. |
|
*/ |
|
#define EOW "~!@$#%^&*()+{}|\"<>?,/;'[]\\=" |
|
#define EOWREGEXP "[^A-Za-z0-9_\\-]" |
|
#define EOWREGEXPDOT "[^A-Za-z0-9_\\-\\.]" |
|
|
|
|
|
/* XinxCodeEdit */ |
|
|
|
/*! |
|
* \class XinxCodeEdit |
|
* \brief XinxCodeEdit is a wrapper of a QCodeEdit editor. |
|
* |
|
* This editor redefine different function used in QTextEdit and TextEditor. |
|
* |
|
* \todo add method : undo(), redo(), cut(), copy(), paste() |
|
* \todo delete \e find method, we use QDocumentSearch. |
|
*/ |
|
|
|
/*! |
|
* \fn void XinxCodeEdit::modelUpdated(QAbstractItemModel * model) |
|
* The model is destroyed or created and need to be updated in the content dialog |
|
*/ |
|
/*! |
|
* \fn void XinxCodeEdit::searchWord(const QString & word) |
|
* The user (By pressing Ctrl+Click) search a word in the document or another. |
|
*/ |
|
/*! |
|
* \fn void XinxCodeEdit::bookmarkToggled(int line, bool enabled) |
|
* Signal is emitted when a bookmark is toogled. |
|
* \param line Line of the bookmark |
|
* \param enabled If enabled, the bookmark is added. |
|
*/ |
|
/*! |
|
* \fn void XinxCodeEdit::copyAvailable(bool y) |
|
* The signal is emited when the state change. \e y is true if copy is available |
|
*/ |
|
/*! |
|
* \fn void XinxCodeEdit::undoAvailable(bool y) |
|
* The signal is emited when the state change. \e y is true if undo is available |
|
*/ |
|
/*! |
|
* \fn void XinxCodeEdit::redoAvailable(bool y) |
|
* The signal is emited when the state change. \e y is true if redo is available |
|
*/ |
|
|
|
/*! |
|
* \enum XinxCodeEdit::FindFlag |
|
* XinxCodeEdit::FindFlag is used in find function. |
|
*/ |
|
|
|
|
|
/*! |
|
* Create a XinxCodeEdit object. |
|
* |
|
* This create the QCodeEdit object with some default option. |
|
*/ |
|
XinxCodeEdit::XinxCodeEdit(QWidget * parent) : QWidget(parent), _text_file_editor(0), m_completer(0) |
|
{ |
|
init(false); |
|
} |
|
|
|
/*! |
|
* Create a XinxCodeEdit object and active action for the editor. |
|
* |
|
* This create the QCodeEdit object with some default option. |
|
*/ |
|
XinxCodeEdit::XinxCodeEdit(bool action, QWidget * parent) : QWidget(parent), _text_file_editor(0), m_completer(0) |
|
{ |
|
init(action); |
|
} |
|
|
|
void XinxCodeEdit::init(bool action) |
|
{ |
|
m_editor = new QCodeEdit(action, this); |
|
setHighlighter(QString()); |
|
|
|
m_editor->editor()->document()->setLineEnding(QDocument::Unix); |
|
m_editor->editor()->setAcceptDrops(true); |
|
m_editor->editor()->setWindowTitle("[*]"); |
|
m_editor->editor()->addInputBinding(this); |
|
m_editor->editor()->setInputBinding(this); |
|
m_editor->editor()->setFrameShape(QFrame::NoFrame); |
|
|
|
QDocumentSearch::Options opt = QDocumentSearch::Silent | QDocumentSearch::HighlightAll; |
|
m_matchingText = new QDocumentSearch(m_editor->editor(), QString(), opt); |
|
|
|
m_editor->addPanel(new QLineMarkPanel, QCodeEdit::West, true); |
|
m_editor->addPanel(new QLineNumberPanel, QCodeEdit::West, true); |
|
m_editor->addPanel(new QFoldPanel, QCodeEdit::West, true); |
|
m_editor->addPanel(new QLineChangePanel, QCodeEdit::West, true); |
|
|
|
QHBoxLayout * layout = new QHBoxLayout; |
|
layout->addWidget(m_editor->editor()); |
|
layout->setSpacing(0); |
|
layout->setMargin(0); |
|
setLayout(layout); |
|
|
|
connect(XINXConfig::self(), SIGNAL(changed()), this, SLOT(updateHighlighter())); |
|
connect(XINXConfig::self(), SIGNAL(changed()), this, SLOT(updateFont())); |
|
connect(m_editor->editor(), SIGNAL(cursorPositionChanged()), this, SLOT(refreshTextHighlighter())); |
|
connect(m_editor->editor(), SIGNAL(copyAvailable(bool)), this, SIGNAL(copyAvailable(bool))); |
|
connect(m_editor->editor(), SIGNAL(redoAvailable(bool)), this, SIGNAL(redoAvailable(bool))); |
|
connect(m_editor->editor(), SIGNAL(undoAvailable(bool)), this, SIGNAL(undoAvailable(bool))); |
|
connect(m_editor->editor()->document(), SIGNAL(markChanged(QDocumentLineHandle*,int,bool)), this, SLOT(slotMarkChanged(QDocumentLineHandle*,int,bool))); |
|
|
|
updateFont(); |
|
} |
|
|
|
//! Destroy the object |
|
XinxCodeEdit::~XinxCodeEdit() |
|
{ |
|
delete m_matchingText; |
|
m_editor->editor()->setInputBinding(0); |
|
m_editor->editor()->removeInputBinding(this); |
|
delete m_editor; |
|
} |
|
|
|
/*! Set the filename used by callSnipet */ |
|
void XinxCodeEdit::setFilename(const QString & filename) |
|
{ |
|
m_filename = filename; |
|
} |
|
|
|
/*! Get the filename set to the editor */ |
|
const QString & XinxCodeEdit::filename() const |
|
{ |
|
return m_filename; |
|
} |
|
|
|
bool XinxCodeEdit::isModified() |
|
{ |
|
return m_editor->editor()->document()->isClean(); |
|
} |
|
|
|
//! Return the current column of the cursor |
|
int XinxCodeEdit::currentColumn() |
|
{ |
|
return m_editor->editor()->cursor().columnNumber(); |
|
} |
|
|
|
//! Return the current row of the cursor |
|
int XinxCodeEdit::currentRow() |
|
{ |
|
return m_editor->editor()->cursor().lineNumber(); |
|
} |
|
|
|
//! Return the number of row of the editor |
|
int XinxCodeEdit::countRow() |
|
{ |
|
return document()->lines(); |
|
} |
|
|
|
//! Returns a pointer to the underlying document. |
|
QDocument * XinxCodeEdit::document() const |
|
{ |
|
return m_editor->editor()->document(); |
|
} |
|
|
|
//! Return the QCodeEdit editor |
|
QEditor * XinxCodeEdit::editor() const |
|
{ |
|
return m_editor->editor(); |
|
} |
|
|
|
void XinxCodeEdit::slotMarkChanged(QDocumentLineHandle* line, int type, bool enabled) |
|
{ |
|
Q_UNUSED(type); |
|
|
|
emit bookmarkToggled(line->line(), enabled); |
|
} |
|
|
|
/*! Clear all bookrmark */ |
|
void XinxCodeEdit::clearBookmark() |
|
{ |
|
int bid = QLineMarksInfoCenter::instance()->markTypeId("bookmark"); |
|
int mark = document()->findNextMark(bid); |
|
while (mark != -1) |
|
{ |
|
emit bookmarkToggled(mark + 1, false); |
|
document()->line(mark).removeMark(bid); |
|
|
|
mark = document()->findNextMark(bid, mark + 1); |
|
} |
|
} |
|
|
|
/*! Previous mark */ |
|
bool XinxCodeEdit::previousBookmark() |
|
{ |
|
int bid = QLineMarksInfoCenter::instance()->markTypeId("bookmark"); |
|
int mark = document()->findPreviousMark(bid, currentRow() - 1); |
|
if (mark == -1) return false; |
|
gotoLine(mark + 1); |
|
return true; |
|
} |
|
|
|
/*! Next bookmark */ |
|
bool XinxCodeEdit::nextBookmark() |
|
{ |
|
int bid = QLineMarksInfoCenter::instance()->markTypeId("bookmark"); |
|
int mark = document()->findNextMark(bid, currentRow() + 1); |
|
if (mark == -1) return false; |
|
gotoLine(mark + 1); |
|
return true; |
|
} |
|
|
|
/*! Show icon bookmark at line \e line. */ |
|
void XinxCodeEdit::setBookmark(int line, bool enabled) |
|
{ |
|
int bid = QLineMarksInfoCenter::instance()->markTypeId("bookmark"); |
|
QDocumentLine documentLine = m_editor->editor()->document()->line(line - 1); |
|
if (enabled) |
|
documentLine.addMark(bid); |
|
else |
|
documentLine.removeMark(bid); |
|
|
|
emit bookmarkToggled(line, enabled); |
|
} |
|
|
|
/*! List of bookmark of the editor. */ |
|
QList<int> XinxCodeEdit::listOfBookmark() |
|
{ |
|
QList<int> bookmarks; |
|
|
|
int bid = QLineMarksInfoCenter::instance()->markTypeId("bookmark"); |
|
int mark = document()->findNextMark(bid); |
|
while (mark != -1) |
|
{ |
|
bookmarks.append(mark + 1); |
|
|
|
mark = document()->findNextMark(bid, mark + 1); |
|
} |
|
|
|
return bookmarks; |
|
} |
|
|
|
/*! Set the list of error. */ |
|
void XinxCodeEdit::setErrors(QList<int> errors) |
|
{ |
|
QList<int> bookmarks; |
|
|
|
int bid = QLineMarksInfoCenter::instance()->markTypeId("error"); |
|
|
|
// Remove old mark |
|
int mark = document()->findNextMark(bid); |
|
while (mark != -1) |
|
{ |
|
QDocumentLine line = m_editor->editor()->document()->line(mark); |
|
line.removeMark(bid); |
|
|
|
mark = document()->findNextMark(bid, mark + 1); |
|
} |
|
|
|
// Activate new marks |
|
foreach(mark, errors) |
|
{ |
|
QDocumentLine line = m_editor->editor()->document()->line(mark); |
|
line.addMark(bid); |
|
} |
|
} |
|
|
|
/*! |
|
* Finds the next occurrence of the string, \e subString, in the document. The search starts at |
|
* the position of the given \e cursor, and proceeds forwards through the document unless |
|
* specified otherwise in the search options. The \e options control the type of search performed. |
|
* |
|
* Returns a cursor with the match selected if \e subString was found; otherwise returns a |
|
* null cursor. |
|
* |
|
* If the given cursor has a selection, the search begins after the selection; otherwise it |
|
* begins at the cursor's position. |
|
* |
|
* By default the search is case-sensitive, and can match text anywhere in the document. |
|
* |
|
* \overload |
|
*/ |
|
QDocumentCursor XinxCodeEdit::find(const QString & subString, const QDocumentCursor & cursor, XinxCodeEdit::FindFlags options) const |
|
{ |
|
QDocumentSearch::Options opt = QDocumentSearch::Silent; |
|
if (options.testFlag(FindCaseSensitively)) opt |= QDocumentSearch::CaseSensitive; |
|
if (options.testFlag(FindWholeWords)) opt |= QDocumentSearch::WholeWords; |
|
QDocumentSearch searchEngine(m_editor->editor(), subString, opt); |
|
|
|
searchEngine.setCursor(cursor); |
|
searchEngine.next(options.testFlag(FindBackward)); |
|
return searchEngine.cursor(); |
|
} |
|
|
|
/*! |
|
* Finds the next occurrence, matching the regular expression \e expr, in the document. |
|
* |
|
* The search starts at the position of the given \e cursor, and proceeds forwards through |
|
* the document unless specified otherwise in the search \e options. The options control |
|
* the type of search performed. The FindCaseSensitively option is ignored for this overload, |
|
* use QRegExp::caseSensitivity instead. |
|
* |
|
* Returns a cursor with the match selected if a match was found; otherwise returns a null |
|
* cursor. |
|
* |
|
* If the given cursor has a selection, the search begins after the selection; otherwise |
|
* it begins at the cursor's position. |
|
* |
|
* By default the search is case-sensitive, and can match text anywhere in the document. |
|
* |
|
* \overload |
|
*/ |
|
QDocumentCursor XinxCodeEdit::find(const QRegExp & expr, const QDocumentCursor & cursor, XinxCodeEdit::FindFlags options) const |
|
{ |
|
QDocumentSearch::Options opt = QDocumentSearch::Silent | QDocumentSearch::RegExp | QDocumentSearch::CaseSensitive; |
|
if (options.testFlag(FindWholeWords)) opt |= QDocumentSearch::WholeWords; |
|
QDocumentSearch searchEngine(m_editor->editor(), expr.pattern(), opt); |
|
|
|
searchEngine.setCursor(cursor); |
|
searchEngine.next(options.testFlag(FindBackward)); |
|
return searchEngine.cursor(); |
|
} |
|
|
|
/*! |
|
* Finds the next occurrence of the string \e exp, using the given \e options. Returns true |
|
* if \e exp was found and changes the cursor to select the match; otherwise returns false. |
|
* |
|
* \overload |
|
*/ |
|
bool XinxCodeEdit::find(const QString & exp, XinxCodeEdit::FindFlags options) |
|
{ |
|
QDocumentCursor c = find(exp, m_editor->editor()->cursor(), options); |
|
return ! c.isNull(); |
|
} |
|
|
|
/*! |
|
* Return the text who is under the cursor. |
|
* \param cursor The cursor to use to look the text. |
|
* \param deleteWord If true, \e textUnderCursor will remove the text returned. |
|
* \param dot If true '.' is considered in a word |
|
*/ |
|
QString XinxCodeEdit::textUnderCursor(const QDocumentCursor & cursor, bool deleteWord, bool dot) |
|
{ |
|
Q_ASSERT(! cursor.isNull()); |
|
QString expr = EOWREGEXPDOT; |
|
if (! dot) |
|
expr = EOWREGEXP; |
|
|
|
QDocumentCursor before(find(QRegExp(expr), cursor, XinxCodeEdit::FindBackward).selectionEnd()); |
|
QDocumentCursor after(find(QRegExp(expr), cursor).selectionStart()); |
|
|
|
QDocumentCursor tc = cursor; |
|
|
|
if ((! before.isNull()) && (before.lineNumber() == tc.lineNumber())) |
|
{ |
|
tc.moveTo(before); |
|
} |
|
else |
|
tc.movePosition(1, QDocumentCursor::StartOfBlock, QDocumentCursor::MoveAnchor) ; |
|
|
|
if ((! after.isNull()) && (after.lineNumber() == tc.lineNumber())) |
|
tc.movePosition(after.position() - (before.position() == -1 ? 0 : before.position()), QDocumentCursor::Right, QDocumentCursor::KeepAnchor) ; |
|
else |
|
tc.movePosition(1, QDocumentCursor::EndOfBlock, QDocumentCursor::KeepAnchor) ; |
|
|
|
QString selection = tc.selectedText().trimmed(); |
|
|
|
if ((! tc.selectedText().trimmed().isEmpty()) && deleteWord) |
|
{ |
|
tc.removeSelectedText(); |
|
m_editor->editor()->setCursor(tc); |
|
} |
|
|
|
return selection; |
|
} |
|
|
|
/*! |
|
* Sets the visible cursor. |
|
* \sa textCursor() |
|
*/ |
|
void XinxCodeEdit::setTextCursor(const QDocumentCursor & cursor) |
|
{ |
|
m_editor->editor()->setCursor(cursor); |
|
} |
|
|
|
/*! |
|
* Returns a copy of the QTextCursor that represents the currently visible cursor. |
|
* Note that changes on the returned cursor do not affect QTextEdit's cursor; |
|
* use setTextCursor() to update the visible cursor. |
|
* |
|
* \sa setTextCursor() |
|
*/ |
|
QDocumentCursor XinxCodeEdit::textCursor() const |
|
{ |
|
return m_editor->editor()->cursor(); |
|
} |
|
|
|
/*! |
|
* Moves the cursor by performing the given operation. |
|
* |
|
* If mode is QTextCursor::KeepAnchor, the cursor selects the text it moves over. |
|
* This is the same effect that the user achieves when they hold down the Shift key |
|
* and move the cursor with the cursor keys. |
|
*/ |
|
void XinxCodeEdit::moveCursor(QDocumentCursor::MoveOperation operation, QDocumentCursor::MoveMode mode) |
|
{ |
|
QDocumentCursor c = m_editor->editor()->cursor(); |
|
c.movePosition(1, operation, mode); |
|
m_editor->editor()->setCursor(c); |
|
} |
|
|
|
//! Return the content of the current selection or all the text if there is no selection. |
|
QString XinxCodeEdit::selection() const |
|
{ |
|
if (m_editor->editor()->cursor().hasSelection()) |
|
return m_editor->editor()->cursor().selectedText(); |
|
else |
|
return toPlainText(); |
|
} |
|
|
|
//! Return the content of the editor |
|
QString XinxCodeEdit::toPlainText() const |
|
{ |
|
return m_editor->editor()->text(); |
|
} |
|
|
|
//! Set the text to \e text |
|
void XinxCodeEdit::setPlainText(const QString & text) |
|
{ |
|
m_editor->editor()->cursor().movePosition(1, QDocumentCursor::Start); |
|
m_editor->editor()->setText(text); |
|
m_editor->editor()->document()->setClean(); |
|
m_editor->editor()->update(); |
|
} |
|
|
|
//! Set the selection to \e text, or replace all the text if there is no selection. |
|
void XinxCodeEdit::setSelection(QString text) |
|
{ |
|
if (m_editor->editor()->cursor().hasSelection()) |
|
{ |
|
m_editor->editor()->cursor().beginEditBlock(); |
|
m_editor->editor()->cursor().removeSelectedText(); |
|
m_editor->editor()->cursor().insertText(text); |
|
m_editor->editor()->cursor().endEditBlock(); |
|
} |
|
else |
|
{ |
|
m_editor->editor()->cursor().beginEditBlock(); |
|
m_editor->editor()->selectAll(); |
|
m_editor->editor()->cursor().removeSelectedText(); |
|
m_editor->editor()->cursor().movePosition(1, QDocumentCursor::Start); |
|
m_editor->editor()->cursor().insertText(text); |
|
m_editor->editor()->cursor().endEditBlock(); |
|
} |
|
} |
|
|
|
//! Set the highlight text with \e text. All text equals are highlighted. |
|
void XinxCodeEdit::setMatchingText(QString text) |
|
{ |
|
if (m_matchingTextString != text) |
|
{ |
|
QDocumentCursor cursor; |
|
cursor.setSilent(true); |
|
if (! text.isEmpty()) |
|
{ |
|
m_matchingText->setCursor(cursor); |
|
m_matchingText->setSearchText(text); |
|
m_matchingText->next(false); |
|
} |
|
else |
|
{ |
|
delete m_matchingText; |
|
QDocumentSearch::Options opt = QDocumentSearch::Silent | QDocumentSearch::HighlightAll; |
|
m_matchingText = new QDocumentSearch(m_editor->editor(), QString(), opt); |
|
} |
|
m_matchingTextString = text; |
|
} |
|
} |
|
|
|
//! Set the completer \e completer to the editor |
|
void XinxCodeEdit::setCompleter(CodeCompletion::Completer * completer) |
|
{ |
|
if (completer != m_completer) |
|
{ |
|
if (m_completer) m_completer->disconnect(this); |
|
completer->setWidget(m_editor->editor()); |
|
connect(completer, SIGNAL(activated(const QModelIndex &)), this, SLOT(insertCompletion(const QModelIndex &))); |
|
m_completer = completer; |
|
} |
|
} |
|
|
|
//! Return the editor setted. |
|
CodeCompletion::Completer * XinxCodeEdit::completer() |
|
{ |
|
return m_completer; |
|
} |
|
|
|
//! Set the current file used to read completion framework |
|
void XinxCodeEdit::setFile(ContentView3::FilePtrWeak file) |
|
{ |
|
_file = file; |
|
} |
|
|
|
//! Get the current file |
|
ContentView3::FilePtrWeak XinxCodeEdit::file() const |
|
{ |
|
return _file; |
|
} |
|
|
|
//! Define the text file editor used has parent of the XinxCodeEdit. |
|
void XinxCodeEdit::setTextFileEditor(TextFileEditor* editor) |
|
{ |
|
_text_file_editor = editor; |
|
} |
|
|
|
//! Get the text file editor used has parent of the XinxCodeEdit |
|
TextFileEditor* XinxCodeEdit::textFileEditor() const |
|
{ |
|
return _text_file_editor; |
|
} |
|
|
|
//! Returns whether text can be pasted from the clipboard into the textedit. |
|
bool XinxCodeEdit::canPaste() |
|
{ |
|
const QMimeData *d = QApplication::clipboard()->mimeData(); |
|
return d && d->hasText(); |
|
} |
|
|
|
/*! |
|
* Convenience function to print the text edit's document to the given printer. |
|
* This is equivalent to calling the print method on the document directly except |
|
* that this function also supports QPrinter::Selection as print range. |
|
*/ |
|
void XinxCodeEdit::print(QPrinter * printer) const |
|
{ |
|
document()->print(printer); |
|
} |
|
|
|
//! Change the document state (modified or not) of the document |
|
void XinxCodeEdit::setModified(bool modified) |
|
{ |
|
if (! modified) |
|
document()->setClean(); |
|
else |
|
{ |
|
//! \todo Find a solution to setModified(true) |
|
qDebug("Call of setModified( true ) but not managed"); |
|
} |
|
} |
|
|
|
/*! |
|
* Set the number of space for a tabulation. |
|
* Warning: This value is global in the application. This is a limitation of QCodeEdit. |
|
* |
|
* \sa tabStopWidth() |
|
*/ |
|
void XinxCodeEdit::setTabStopWidth(int width) |
|
{ |
|
m_editor->editor()->document()->setTabStop(width); |
|
} |
|
|
|
/*! |
|
* Get the number of space for a tabulation. |
|
* Warning: This value is global in the application. This is a limitation of QCodeEdit. |
|
* |
|
* \sa setTabStopWidth() |
|
*/ |
|
int XinxCodeEdit::tabStopWidth() const |
|
{ |
|
return m_editor->editor()->document()->tabStop(); |
|
} |
|
|
|
//! Change the read only property of the editor |
|
void XinxCodeEdit::setReadOnly(bool readonly) |
|
{ |
|
m_editor->editor()->setFlag(QEditor::ReadOnly, readonly); |
|
} |
|
|
|
/*! Return true if the editor is read only */ |
|
bool XinxCodeEdit::isReadOnly() const |
|
{ |
|
return m_editor->editor()->flag(QEditor::ReadOnly); |
|
} |
|
|
|
//! Change the highlighter to \e highlighter |
|
void XinxCodeEdit::setHighlighter(const QString & highlighter) |
|
{ |
|
setHighlighter(highlighter, XINXConfig::self()); |
|
} |
|
|
|
//! Change the highlighter to \e highlighter but with color settings of \e config |
|
void XinxCodeEdit::setHighlighter(const QString & highlighter, XINXConfig * config) |
|
{ |
|
if (highlighter.isEmpty()) |
|
{ |
|
document()->setFormatScheme(config->languageFactory()->defaultFormatScheme()); |
|
config->languageFactory()->setLanguage(m_editor->editor(), "None"); |
|
return; |
|
} |
|
|
|
QFormatScheme * scheme = config->scheme(highlighter); |
|
if (! scheme) |
|
scheme = config->languageFactory()->defaultFormatScheme(); |
|
document()->setFormatScheme(scheme); |
|
|
|
config->languageFactory()->setLanguage(m_editor->editor(), highlighter); |
|
} |
|
|
|
/*! |
|
* Called when the configuration change and it's necessary to update the highlighter. |
|
* If no highlighter is used, this function do nothing. |
|
*/ |
|
void XinxCodeEdit::updateHighlighter() |
|
{ |
|
document()->setFormatScheme(document()->formatScheme()); |
|
} |
|
|
|
//! Update the font (when the configuration change) |
|
void XinxCodeEdit::updateFont() |
|
{ |
|
QFont font = XINXConfig::self()->config().editor.defaultFormat; |
|
QFontMetrics fm(font); |
|
QDocument::setFont(font); |
|
QDocument::setTabStop(XINXConfig::self()->config().editor.tabulationSize); |
|
|
|
if (XINXConfig::self()->config().editor.showTabulationAndSpace) |
|
QDocument::setShowSpaces(QDocument::ShowTrailing | QDocument::ShowLeading | QDocument::ShowTabs); |
|
else |
|
QDocument::setShowSpaces(QDocument::ShowNone); |
|
|
|
m_editor->editor()->setLineWrapping(XINXConfig::self()->config().editor.wrapLine); |
|
} |
|
|
|
//! Insert the selection where the cursor is (and replace the selection if any). This method indent the text. |
|
void XinxCodeEdit::insertText(const QString & text) |
|
{ |
|
QDocumentCursor cursor = textCursor(); |
|
|
|
QString indent = cursor.line().previous().text(); |
|
indent = indent.left(indent.indexOf(QRegExp("\\S"))); |
|
|
|
QStringList lines = text.split("\n", QString::KeepEmptyParts); |
|
|
|
QStringListIterator i(lines); |
|
if (i.hasNext()) |
|
{ |
|
cursor.insertText(i.next()); |
|
if (i.hasNext()) |
|
cursor.insertLine(); |
|
} |
|
|
|
while (i.hasNext()) |
|
{ |
|
cursor.insertText(indent + i.next()); |
|
if (i.hasNext()) |
|
cursor.insertLine(); |
|
} |
|
setTextCursor(cursor); |
|
} |
|
|
|
/*! Insert the completion based on the QCompleter */ |
|
void XinxCodeEdit::insertCompletion(const QModelIndex& index) |
|
{ |
|
if (m_completer) |
|
{ |
|
m_completer->complete(index); |
|
} |
|
} |
|
|
|
/*! In the editor go to the line \e line. */ |
|
void XinxCodeEdit::gotoLine(int line) |
|
{ |
|
/* Five line before */ |
|
QDocumentCursor cursorB(m_editor->editor()->document(), line - 5); |
|
if (!cursorB.isNull()) |
|
m_editor->editor()->setCursor(cursorB); |
|
|
|
/* Five line after */ |
|
QDocumentCursor cursorA(m_editor->editor()->document(), line + 5); |
|
if (!cursorA.isNull()) |
|
m_editor->editor()->setCursor(cursorA); |
|
|
|
QDocumentCursor cursor(m_editor->editor()->document(), line - 1); |
|
if (!cursor.isNull()) |
|
m_editor->editor()->setCursor(cursor); |
|
} |
|
|
|
/*! Duplicate the current line or the current selection in the editor. */ |
|
void XinxCodeEdit::duplicateLines() |
|
{ |
|
QDocumentCursor cursor(textCursor()); |
|
QString text; |
|
if (cursor.hasSelection()) |
|
text = cursor.selectedText(); |
|
else |
|
text = cursor.line().text(); |
|
|
|
if ((!cursor.hasSelection()) && cursor.line() == cursor.selectionStart().line() && cursor.line() == cursor.selectionEnd().line()) |
|
text += "\n"; |
|
|
|
|
|
QDocumentCursor selStart = cursor.selectionStart(); |
|
cursor.beginEditBlock(); |
|
cursor.moveTo(selStart); |
|
if (text.contains('\n')) |
|
cursor.movePosition(0, QDocumentCursor::StartOfLine); |
|
cursor.insertText(text); |
|
cursor.movePosition(text.length(), QDocumentCursor::Right, QDocumentCursor::KeepAnchor); |
|
cursor.endEditBlock(); |
|
setTextCursor(cursor); |
|
} |
|
|
|
/*! |
|
* Move the current line up. |
|
* Swap the current line with the line above. |
|
* \sa moveLineDown() |
|
*/ |
|
void XinxCodeEdit::moveLineUp() |
|
{ |
|
QDocumentCursor cursor(textCursor()); |
|
|
|
int selectionLength = 0; |
|
QDocumentCursor selectionStart = cursor.selectionStart(), |
|
selectionEnd = cursor.selectionEnd(); |
|
selectionStart.setAutoUpdated(false); |
|
selectionEnd.setAutoUpdated(false); |
|
|
|
if (selectionEnd.isValid()) selectionLength = cursor.selectedText().length(); |
|
|
|
if (selectionEnd.columnNumber() == 0) selectionEnd.movePosition(1, QDocumentCursor::Left); |
|
if (selectionStart.lineNumber() == 0) return; // No move when we are at the first line |
|
|
|
cursor.beginEditBlock(); |
|
|
|
QString text; |
|
QDocumentLine currentLine = selectionStart.line(); |
|
cursor.moveTo(currentLine, 0); |
|
do |
|
{ |
|
text += currentLine.text() + "\n"; |
|
cursor.movePosition(1, QDocumentCursor::NextLine, QDocumentCursor::KeepAnchor); |
|
|
|
currentLine = currentLine.next(); |
|
} |
|
while (currentLine.isValid() && (currentLine <= selectionEnd.line())); |
|
|
|
cursor.removeSelectedText(); |
|
|
|
cursor.movePosition(1, QDocumentCursor::PreviousLine, QDocumentCursor::MoveAnchor); |
|
cursor.movePosition(1, QDocumentCursor::StartOfLine, QDocumentCursor::MoveAnchor); |
|
cursor.insertText(text); |
|
|
|
cursor.moveTo(selectionStart.lineNumber() - 1, selectionStart.columnNumber()); |
|
if (selectionLength > 0) |
|
cursor.movePosition(selectionLength, QDocumentCursor::Right, QDocumentCursor::KeepAnchor); |
|
|
|
cursor.endEditBlock(); |
|
setTextCursor(cursor); |
|
} |
|
|
|
/*! |
|
* Move the current line down. |
|
* Swap the current line with the line after. |
|
* \sa moveLineUp() |
|
*/ |
|
void XinxCodeEdit::moveLineDown() |
|
{ |
|
QDocumentCursor cursor(textCursor()); |
|
|
|
int selectionLength = 0; |
|
QDocumentCursor selectionStart = cursor.selectionStart(), |
|
selectionEnd = cursor.selectionEnd(); |
|
if (selectionEnd.isValid()) selectionLength = cursor.selectedText().length(); |
|
|
|
if (selectionEnd.columnNumber() == 0) selectionEnd.movePosition(1, QDocumentCursor::Left); |
|
if (((selectionEnd.lineNumber() + 1) == m_editor->editor()->document()->lines()) || ((selectionStart.lineNumber() + 1) == m_editor->editor()->document()->lines())) return; // No move at end of document |
|
|
|
cursor.beginEditBlock(); |
|
|
|
QString text; |
|
QDocumentLine currentLine = selectionStart.line(); |
|
cursor.moveTo(currentLine, 0); |
|
do |
|
{ |
|
text += currentLine.text() + "\n"; |
|
cursor.movePosition(1, QDocumentCursor::NextLine, QDocumentCursor::KeepAnchor); |
|
|
|
currentLine = currentLine.next(); |
|
} |
|
while (currentLine.isValid() && (currentLine <= selectionEnd.line())); |
|
|
|
cursor.removeSelectedText(); |
|
|
|
if ((cursor.lineNumber() + 1) < m_editor->editor()->document()->lines()) |
|
{ |
|
cursor.movePosition(1, QDocumentCursor::NextLine, QDocumentCursor::MoveAnchor); |
|
cursor.movePosition(1, QDocumentCursor::StartOfLine, QDocumentCursor::MoveAnchor); |
|
} |
|
else |
|
{ |
|
cursor.movePosition(1, QDocumentCursor::EndOfLine, QDocumentCursor::MoveAnchor); |
|
cursor.insertText("\n"); |
|
text.chop(1); |
|
} |
|
cursor.insertText(text); |
|
|
|
cursor.moveTo(selectionStart.lineNumber() + 1, selectionStart.columnNumber()); |
|
if (selectionLength > 0) |
|
cursor.movePosition(selectionLength, QDocumentCursor::Right, QDocumentCursor::KeepAnchor); |
|
|
|
cursor.endEditBlock(); |
|
setTextCursor(cursor); |
|
} |
|
|
|
void XinxCodeEdit::uploSelectedText(bool upper) |
|
{ |
|
QDocumentCursor cursor(textCursor()); |
|
|
|
QDocumentCursor startPos = cursor.selectionStart(); |
|
QDocumentCursor endPos = cursor.selectionEnd(); |
|
|
|
QString text = cursor.selectedText(); |
|
|
|
if (upper) |
|
text = text.toUpper(); |
|
else |
|
text = text.toLower(); |
|
|
|
cursor.beginEditBlock(); |
|
|
|
cursor.removeSelectedText(); |
|
cursor.insertText(text); |
|
|
|
cursor.moveTo(startPos); |
|
cursor.movePosition(endPos.position() - startPos.position(), QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor); |
|
|
|
cursor.endEditBlock(); |
|
|
|
setTextCursor(cursor); |
|
} |
|
|
|
/*! Replace the selected text by upper case character the parameter. */ |
|
void XinxCodeEdit::upperSelectedText() |
|
{ |
|
uploSelectedText(true); |
|
} |
|
|
|
/*! Replace the selected text by lower case character. */ |
|
void XinxCodeEdit::lowerSelectedText() |
|
{ |
|
uploSelectedText(false); |
|
} |
|
|
|
/*! |
|
* Indent or unindent the selected text depending on the parameter. |
|
* \param unindent If false (by default) the text is indented. (the character \\t is added), else the text is unindented. |
|
*/ |
|
void XinxCodeEdit::indent(bool unindent) |
|
{ |
|
if (! unindent) |
|
m_editor->editor()->indentSelection(); |
|
else |
|
m_editor->editor()->unindentSelection(); |
|
} |
|
|
|
/*! |
|
* Comment or Uncomment the selected text depending on the parrameter. |
|
* If a part of a text is already (un)commented, the balise is moved to comment all the text. |
|
* Warning: If you comment code with comment, the comment can be merged with code. |
|
* \param uncomment If false (by default) the text is commented, else the text is uncommented |
|
*/ |
|
void XinxCodeEdit::commentSelectedText(bool uncomment) |
|
{ |
|
Q_UNUSED(uncomment); |
|
throw XinxException(tr("Can't comment this type of document")); |
|
} |
|
|
|
/*! |
|
* Return if the editor can comment the code |
|
*/ |
|
bool XinxCodeEdit::isCommentAvailable() |
|
{ |
|
return false; |
|
} |
|
|
|
//! Refresh the text highlighter (in case the cursor position change) |
|
void XinxCodeEdit::refreshTextHighlighter() |
|
{ |
|
if (XINXConfig::self()->config().editor.autoHighlight && (!textCursor().isNull())) |
|
setMatchingText(textUnderCursor(textCursor(), false, false)); |
|
} |
|
|
|
/*! Highlight all text equals of the current word under the cursor */ |
|
void XinxCodeEdit::callTextHighlighter() |
|
{ |
|
setMatchingText(textUnderCursor(textCursor(), false, false)); |
|
} |
|
|
|
QString XinxCodeEdit::id() const |
|
{ |
|
return "xinx"; |
|
} |
|
|
|
QString XinxCodeEdit::name() const |
|
{ |
|
return "XINX Binding"; |
|
} |
|
|
|
/*! |
|
* Process to do when a user press a key |
|
* This method is called when the editor ask to process some shortcut. |
|
*/ |
|
bool XinxCodeEdit::localKeyPressExecute(QKeyEvent * e) |
|
{ |
|
if ((e->key() == Qt::Key_Home) && (e->modifiers() == Qt::ShiftModifier || e->modifiers() == Qt::NoModifier)) |
|
{ |
|
key_home(e->modifiers() == Qt::ShiftModifier); |
|
e->accept(); |
|
return false; |
|
} |
|
else if ((e->key() == Qt::Key_Tab) && ((e->modifiers() == Qt::NoModifier) || (e->modifiers() == Qt::ShiftModifier))) |
|
{ |
|
// TODO: May be replace by an action of the texteditor... |
|
indent(e->modifiers() != Qt::NoModifier); |
|
e->accept(); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void XinxCodeEdit::key_home(bool select) |
|
{ |
|
QDocumentCursor cursor = textCursor(); |
|
int col = cursor.columnNumber(); |
|
cursor.movePosition(1, QDocumentCursor::StartOfLine, select ? QDocumentCursor::KeepAnchor : QDocumentCursor::MoveAnchor); |
|
QDocumentCursor cursorStart(cursor); |
|
QDocumentLine b = cursorStart.line(); |
|
int i = 0; |
|
while ((i < b.text().size()) && (i >= 0) && (b.text().at(i) == ' ' || b.text().at(i) == '\t')) |
|
{ |
|
cursorStart.movePosition(1, QDocumentCursor::NextCharacter, select ? QDocumentCursor::KeepAnchor : QDocumentCursor::MoveAnchor); |
|
i++; |
|
} |
|
if (col == cursorStart.columnNumber()) |
|
setTextCursor(cursor); |
|
else |
|
setTextCursor(cursorStart); |
|
} |
|
|
|
bool XinxCodeEdit::keyPressEvent(QKeyEvent * e, QEditor * editor) |
|
{ |
|
Q_UNUSED(editor); |
|
|
|
if (m_completer && m_completer->popup()->isVisible()) |
|
{ |
|
// The following keys are forwarded by the completer to the widget |
|
switch (e->key()) |
|
{ |
|
case Qt::Key_Enter: |
|
case Qt::Key_Return: |
|
case Qt::Key_Escape: |
|
case Qt::Key_Tab: |
|
case Qt::Key_Backtab: |
|
e->ignore(); |
|
return true; // let the completer do default behavior |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_E); // CTRL+E |
|
bool callParent = false; |
|
if (!m_completer || !isShortcut) |
|
callParent = localKeyPressExecute(e); |
|
|
|
if (callParent) |
|
{ |
|
return false; |
|
} |
|
|
|
postKeyPressEvent(e, editor); |
|
|
|
return true; |
|
} |
|
|
|
void XinxCodeEdit::postKeyPressEvent(QKeyEvent * e, QEditor * editor) |
|
{ |
|
if (e->isAccepted()) |
|
processKeyPress(e); |
|
|
|
const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier); |
|
if (!m_completer || (ctrlOrShift && e->text().isEmpty())) |
|
{ |
|
return ; |
|
} |
|
|
|
static QString eow(EOW); // end of word |
|
bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_E); // CTRL+E |
|
bool hasModifier = (e->modifiers() & (Qt::ControlModifier | Qt::AltModifier)); // && !ctrlOrShift; |
|
QString completionPrefix = textUnderCursor(textCursor()); |
|
|
|
if (!isShortcut && (hasModifier || e->text().isEmpty() || completionPrefix.length() < 2 || eow.contains(e->text().right(1)))) |
|
{ |
|
m_completer->popup()->hide(); |
|
return ; |
|
} |
|
|
|
m_completer->complete(true, completionPrefix); |
|
} |
|
|
|
/*! |
|
* Process to do when a user press a key. |
|
* This method is called when the editor ask to add some text automatically. (ie: |
|
* close a bracket, ...) |
|
*/ |
|
bool XinxCodeEdit::processKeyPress(QKeyEvent *) |
|
{ |
|
return true; |
|
} |
|
|
|
void XinxCodeEdit::postMousePressEvent(QMouseEvent *event, QEditor * editor) |
|
{ |
|
if ((event->type() == QEvent::MouseButtonPress) && (event->button() == Qt::LeftButton) && (event->modifiers() == Qt::ControlModifier)) |
|
{ |
|
QDocumentCursor cur = editor->cursor(); |
|
editor->setCursor(cur); |
|
QMetaObject::invokeMethod(this, "searchWord", Qt::QueuedConnection, Q_ARG(QString, textUnderCursor(textCursor(), false))); |
|
} |
|
|
|
} |
|
|
|
void XinxCodeEdit::insertDragAndDropText(const QString& text) |
|
{ |
|
insertText(text); |
|
} |
|
|
|
bool XinxCodeEdit::dropEvent(QDropEvent *e, QEditor *editor) |
|
{ |
|
if (e && e->mimeData() && e->mimeData()->hasFormat("application/snipet.id.list")) |
|
{ |
|
e->acceptProposedAction(); |
|
|
|
editor->cursor().beginEditBlock(); |
|
editor->cursor().clearSelection(); |
|
|
|
editor->clearCursorMirrors(); |
|
setTextCursor(editor->cursorForPosition(editor->mapToContents(e->pos()))); |
|
|
|
QByteArray itemData = e->mimeData()->data("application/snipet.id.list"); |
|
QDataStream stream(&itemData, QIODevice::ReadOnly); |
|
|
|
while (! stream.atEnd()) |
|
{ |
|
int id; |
|
QString type, name; |
|
stream >> id >> type >> name; |
|
|
|
if (type == "SNIPET") |
|
{ |
|
QString result; |
|
if (SnipetManager::self()->callSnipet(id, &result, qApp->activeWindow())) |
|
{ |
|
insertText(result); |
|
} |
|
} |
|
} |
|
|
|
editor->cursor().endEditBlock(); |
|
editor->selectionChange(); |
|
|
|
return true; |
|
} |
|
else if (e && e->mimeData() && e->mimeData()->hasText()) |
|
{ |
|
e->acceptProposedAction(); |
|
|
|
editor->cursor().beginEditBlock(); |
|
editor->cursor().clearSelection(); |
|
|
|
editor->clearCursorMirrors(); |
|
setTextCursor(editor->cursorForPosition(editor->mapToContents(e->pos()))); |
|
if (_text_file_editor) |
|
{ |
|
_text_file_editor->updateContext(); |
|
} |
|
|
|
QString itemData = e->mimeData()->text(); |
|
insertDragAndDropText(itemData); |
|
|
|
editor->cursor().endEditBlock(); |
|
editor->selectionChange(); |
|
|
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
/* XinxCodeEditAction */ |
|
|
|
/*! |
|
* XinxCodeEditAction is a wrapper of QCodeEdit editor. This editor active |
|
* action in QCodeEdit. |
|
*/ |
|
|
|
/*! |
|
* Create a XinxCodeEdit object. |
|
* |
|
* This create the QCodeEdit object with some default option. |
|
*/ |
|
XinxCodeEditAction::XinxCodeEditAction(QWidget * parent) : XinxCodeEdit(true, parent) |
|
{ |
|
}
|
|
|