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.
 
 
 
 
 
 

795 lines
19 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/textfileeditor.h"
#include "editors/xinxcodeedit.h"
#include "editors/bookmarktexteditorinterface.h"
#include "actions/actioninterface.h"
#include "plugins/xinxpluginsloader.h"
#include "project/xinxprojectproject.h"
#include "session/sessioneditor.h"
#include "core/xinxconfig.h"
#include "borderlayout.h"
#include <plugins/xinxpluginelement.h>
#include <actions/actionmanager.h>
#include <plugins/interfaces/codecompletion.h>
#include <contentview3/parserfactory.h>
#include <contentview3/parser.h>
#include <contentview3/treemodel.h>
#include <contentview3/filenode.h>
#include <codecompletion/completer.h>
#include <codecompletion/model.h>
#include <codecompletion/pool.h>
// Qt header
#include <QScrollBar>
#include <QPainter>
#include <QHBoxLayout>
#include <QFileInfo>
#include <QDir>
#include <QMessageBox>
#include <QTextStream>
#include <QApplication>
#include <QLabel>
#include <QPushButton>
#include <QMessageBox>
#include <QAction>
#include <QContextMenuEvent>
#include <QMenu>
#include <QCompleter>
#include <QAbstractItemView>
#include <QTextCodec>
#include <QBuffer>
/* TextFileEditor */
#ifdef Q_WS_X11
# define DEFAULT_EOL TextFileEditor::UnixEndOfLine
#else
#ifdef Q_WS_MAC
# define DEFAULT_EOL TextFileEditor::MacEndOfLine
#else
# define DEFAULT_EOL TextFileEditor::WindowsEndOfLine
#endif
#endif
/*!
* \ingroup XinxEditors
* \class TextFileEditor
* Class used to represent a file editor. A file editor have a XinxCodeEditor class, who show the line in the document,
* folding, ....
* The File Editor is a subclass of Editor and implements all its pure virtual method.
*
* The File Editor propose a method to read the FileName : getFileName(), and the default suffix (for save as) to use (getSuffix).
* If a file is open, getTitle() return the file name otherwise it return noname.
*
* FileEditor has also two methode for load and save file.
*
* An \e TextFileEditor load and unload the content view model editor when
* reading the device. The content model is based on a ContentViewParser and
* some ContentViewNode.
*/
/*!
* \enum TextFileEditor::EndOfLineType
* \brief Type of End Of Line that the editor can understand.
*/
/*!
* \var TextFileEditor::WindowsEndOfLine
* \brief The end of line is terminated by \\r\\n
*/
/*!
* \var TextFileEditor::UnixEndOfLine
* \brief The end of line is terminated by \\n
*/
/*!
* \var TextFileEditor::MacEndOfLine
* \brief The end of line is terminated by \\r\\n
*/
/*!
* Construct a TextFileEditor with the help of a XinxCodeEdit and a parent.
* \param editor XinxCodeEdit to use to print file to screen (center widget)
* \param parent Parent and containers of the editor.
*/
TextFileEditor::TextFileEditor(XinxCodeEdit* editor, QWidget* parent): AbstractEditor(parent), _modification_timer(0), _move_timer(0), m_codec(XINXConfig::self()->config().editor.defaultTextCodec), m_view(editor), m_eol(DEFAULT_EOL), m_bookmarkInterface(0), _model(0), _completion_model(0), _completer(0)
{
initObjects();
}
/*! Destructor of the FileEditor.*/
TextFileEditor::~TextFileEditor()
{
ErrorManager::self()->clearMessages(lastFileName());
}
/*!
* \fn void TextFileEditor::selectionAvailable(bool yes);
* \brief Signal emitted when a text is selected or deselected.
* \param yes The value is true if a part of the text is selected, else the value is false.
*/
/*!
* \fn void TextFileEditor::codecChanged();
* \brief This signal is emited when the codec is modified.
*/
void TextFileEditor::initObjects()
{
_modification_timer.reset(new QTimer);
_modification_timer->setSingleShot(true);
_modification_timer->setInterval(XINXConfig::self()->config().editor.automaticModelRefreshTimeout);
connect(_modification_timer.data(), SIGNAL(timeout()), this, SLOT(updateModel()));
_move_timer.reset(new QTimer);
_move_timer->setSingleShot(true);
_move_timer->setInterval(100);
connect(_move_timer.data(), SIGNAL(timeout()), this, SLOT(updateContext()));
// Set the internal editor used by textfileeditor
if (! m_view)
m_view = new XinxCodeEdit(this);
else
m_view->setParent(this);
m_view->setTextFileEditor(this);
setContextMenuPolicy(Qt::DefaultContextMenu);
m_view->setContextMenuPolicy(Qt::NoContextMenu);
m_view->editor()->setContextMenuPolicy(Qt::NoContextMenu);
m_bookmarkInterface = new BookmarkTextEditorInterface(this);
m_bookmarkInterface->setTextEditor(m_view);
connect(m_view, SIGNAL(copyAvailable(bool)), this, SIGNAL(copyAvailable(bool)));
connect(m_view, SIGNAL(copyAvailable(bool)), this, SIGNAL(selectionAvailable(bool)));
connect(m_view, SIGNAL(undoAvailable(bool)), this, SIGNAL(undoAvailable(bool)));
connect(m_view, SIGNAL(redoAvailable(bool)), this, SIGNAL(redoAvailable(bool)));
connect(m_view->editor(), SIGNAL(contentModified(bool)), this, SLOT(setModified(bool)));
connect(m_view->document(), SIGNAL(contentsChanged()), this, SLOT(textChanged()));
connect(m_view->editor(), SIGNAL(cursorPositionChanged()), this, SLOT(cursorPositionChanged()));
connect(m_view, SIGNAL(searchWord(QString)), this, SLOT(searchWord(QString)));
connect(ErrorManager::self(), SIGNAL(changed()), this, SLOT(errorChanged()));
connect(m_view->editor(), SIGNAL(textEdited(QKeyEvent*)), this, SLOT(updateCodec()));
/* Completer */
_completer = new CodeCompletion::Completer(this);
_completion_model = new CodeCompletion::Model(_completer);
_completer->setModel(_completion_model);
_completer->setContext(_context);
_completer->setEditor(this);
_model = new ContentView3::TreeModel(this);
setFile(ContentView3::File::create());
}
void TextFileEditor::setProject(XinxProject::ProjectPtr project)
{
if (AbstractEditor::project().data() != project.data())
{
AbstractEditor::setProject(project);
_file->setProject(project);
}
}
void TextFileEditor::initLayout()
{
QHBoxLayout * hbox = new QHBoxLayout;
hbox->addWidget(m_view);
hbox->setMargin(0);
setLayout(hbox);
}
BookmarkEditorInterface * TextFileEditor::bookmarkInterface()
{
return m_bookmarkInterface;
}
BookmarkTextEditorInterface * TextFileEditor::bookmarkTextInterface()
{
return m_bookmarkInterface;
}
/*!
* Return the text editor corresponding on the file editor. This editor can't be null.
* \return The text editor widget used by the file editor.
*/
XinxCodeEdit * TextFileEditor::textEdit() const
{
return m_view;
}
/*! Method used to select all the text in the editor. The call is sent to the TextEditor. */
void TextFileEditor::selectAll()
{
m_view->editor()->selectAll();
}
void TextFileEditor::loadFromFile(const QString & fileName)
{
_file->setFilename(fileName);
AbstractEditor::loadFromFile(fileName);
m_view->setFilename(fileName);
}
void TextFileEditor::saveToFile(const QString & fileName)
{
_file->setFilename(fileName);
AbstractEditor::saveToFile(fileName);
m_view->setFilename(fileName);
}
void TextFileEditor::detectEOL(QIODevice & d)
{
// Get the EOL of the file.
char c;
while (d.getChar(&c))
{
if (c == '\r')
{
m_eol = MacEndOfLine;
if (d.getChar(&c) && (c == '\n'))
{
m_eol = WindowsEndOfLine;
}
break;
}
else if (c == '\n')
{
m_eol = UnixEndOfLine;
break;
}
}
d.reset();
}
void TextFileEditor::detectCodec(QIODevice & d)
{
Q_UNUSED(d);
setCodecName(XINXConfig::self()->config().editor.defaultTextCodec);
}
void TextFileEditor::loadFromDevice(QIODevice & d)
{
_modification_timer->stop();
detectEOL(d);
detectCodec(d);
QTextStream text(&d);
text.setCodec(codec());
QString textBuffer = text.readAll();
if (m_eol == MacEndOfLine)
{
textBuffer.replace("\r", "\n");
}
m_view->setPlainText(textBuffer);
_modification_timer->stop();
updateModel();
}
void TextFileEditor::saveToDevice(QIODevice & d)
{
if (XINXConfig::self()->config().editor.autoindentOnSaving == "true") autoIndent();
m_eol = DEFAULT_EOL; // Don't conserve the EOL
d.setTextModeEnabled(true);
QTextStream text(&d);
text.setCodec(codec()); // Use the real codec on save
text << m_view->toPlainText();
}
void TextFileEditor::setModified(bool isModified)
{
m_view->setModified(isModified);
AbstractEditor::setModified(isModified);
}
CodeCompletion::Context TextFileEditor::context()
{
return _context;
}
/*!
* The default implementation return null
* \sa contentViewModel()
*/
QAbstractItemModel * TextFileEditor::model() const
{
return contentViewModel();
}
/*!
* Return the model used in the edi tor as model() but with the type ContentViewModel
* \sa model()
*/
ContentView3::TreeModel * TextFileEditor::contentViewModel() const
{
return _model;
}
void TextFileEditor::initSearch(SearchOptions & options)
{
m_cursorStart = m_view->textCursor();
m_cursorEnd = QDocumentCursor();
QDocumentCursor selectionStart = m_cursorStart.selectionStart();
QDocumentCursor selectionEnd = m_cursorStart.selectionEnd();
if (options.testFlag(SEARCH_FROM_START))
{
if (! options.testFlag(BACKWARD))
m_cursorStart.movePosition(1, QDocumentCursor::Start, QDocumentCursor::MoveAnchor);
else
m_cursorStart.movePosition(1, QDocumentCursor::End, QDocumentCursor::MoveAnchor);
options &= ~ SearchOptions(SEARCH_FROM_START);
}
else if (options.testFlag(ONLY_SELECTION) && ! options.testFlag(BACKWARD))
{
m_cursorStart.moveTo(selectionStart);
m_cursorEnd = m_cursorStart;
m_cursorEnd.movePosition(selectionEnd.position() - selectionStart.position(), QDocumentCursor::Right, QDocumentCursor::MoveAnchor);
}
else if (options.testFlag(ONLY_SELECTION) && options.testFlag(BACKWARD))
{
m_cursorStart.moveTo(selectionEnd);
m_cursorEnd = m_cursorStart;
m_cursorEnd.movePosition(selectionEnd.position() - selectionStart.position(), QDocumentCursor::Left, QDocumentCursor::MoveAnchor);
}
else if (options.testFlag(BACKWARD))
{
m_cursorStart.moveTo(selectionStart);
}
m_view->setTextCursor(m_cursorStart);
}
bool TextFileEditor::find(const QString & text, SearchOptions options)
{
QDocumentCursor finded, tc = m_cursorStart;
if (options.testFlag(BACKWARD))
tc.moveTo(tc.selectionStart());
else
tc.moveTo(tc.selectionEnd());
XinxCodeEdit::FindFlags flags;
if (options.testFlag(BACKWARD)) flags ^= XinxCodeEdit::FindBackward;
if (options.testFlag(MATCH_CASE)) flags ^= XinxCodeEdit::FindCaseSensitively;
if (options.testFlag(WHOLE_WORDS)) flags ^= XinxCodeEdit::FindWholeWords;
if (options.testFlag(REGULAR_EXPRESSION))
{
finded = m_view->find(QRegExp(text), tc, flags);
}
else
{
finded = m_view->find(text, tc, flags);
}
if (! finded.isNull())
{
if (options.testFlag(ONLY_SELECTION))
{
if ((! options.testFlag(BACKWARD)) && (m_cursorEnd < finded))
{
return false;
}
else if (options.testFlag(BACKWARD) && (finded < m_cursorEnd))
{
return false;
}
}
m_cursorStart = finded;
m_view->setTextCursor(finded);
}
return !finded.isNull();
}
void TextFileEditor::replace(const QString & from, const QString & to, SearchOptions options)
{
Q_ASSERT(! m_cursorStart.isNull());
if (! options.testFlag(REGULAR_EXPRESSION))
{
m_cursorStart.removeSelectedText();
m_cursorStart.insertText(to);
return;
}
QRegExp expression(from);
expression.indexIn(m_cursorStart.selectedText());
QStringList list = expression.capturedTexts();
QString result(to);
int index = 1;
QStringList::iterator it = list.begin();
while (it != list.end())
{
result = result.replace(QString("\\%1").arg(index), *it);
it++;
index++;
}
m_cursorStart.removeSelectedText();
m_cursorStart.insertText(result);
}
//! Create a parser for the text editor (based on the paser type of the interface)
ContentView3::Parser * TextFileEditor::createParser()
{
Q_ASSERT_X(fileTypePluginInterface(), "TextFileEditor::createParser", "An interface must be defined on an editor");
if (fileTypePluginInterface())
{
const QString parserName = fileTypePluginInterface()->parserType();
if (parserName.isEmpty()) return NULL;
return ContentView3::ParserFactory::getParserByType(parserName);
}
return NULL;
}
/*!
* \brief Update content view and completion with the content of the text editor.
*
* The default implementation create the parser by using \e createParser() with the content
* of the editor, and run it.
*/
void TextFileEditor::updateModel()
{
updateCodec();
ContentView3::Parser * parser = createParser();
if (parser)
{
connect(parser, SIGNAL(jobEnding()), this, SLOT(fileParsed()));
QBuffer * buffer = new QBuffer;
if (codec())
{
buffer->setData(codec()->fromUnicode(m_view->toPlainText()));
}
else
{
buffer->setData(qPrintable(m_view->toPlainText()));
}
parser->setFile(_file);
if (project())
{
parser->setWorkingPath(project()->projectPath());
}
parser->setDevice(buffer);
project()->cache()->addFileToCache(parser, true, ContentView3::Cache::NONE);
}
emit contentChanged();
}
void TextFileEditor::fileParsed()
{
_model->reload();
//_completion_model->updateModel ();
qDebug() << "File " << _file->filename() << " reloaded.";
}
void TextFileEditor::textChanged()
{
_modification_timer->start();
}
void TextFileEditor::errorChanged()
{
QList<int> lines;
foreach(ErrorManager::Error err, ErrorManager::self()->errors().value(lastFileName()))
{
if (! lines.contains(err.line))
lines << err.line - 1;
}
m_view->setErrors(lines);
}
void TextFileEditor::setFile(ContentView3::FilePtr file)
{
if (_file != file)
{
_file = file;
_file->setProject(project());
_context.setFile(_file.toWeakRef());
_model->setFile(_file);
m_view->setFile(_file.toWeakRef());
}
}
ContentView3::FilePtr TextFileEditor::file() const
{
return _file;
}
//! Set the codec to \e text
void TextFileEditor::setCodecName(const QString & text)
{
if (m_codec != text)
{
m_codec = text;
emit codecChanged();
}
}
//! Get the codec name
const QString & TextFileEditor::codecName() const
{
return m_codec;
}
//! The codec used to read and write the file. By Default, the codec is defined in options.
QTextCodec * TextFileEditor::codec() const
{
QTextCodec * codec = QTextCodec::codecForName(m_codec.toAscii());
if (! codec)
{
codec = QTextCodec::codecForLocale();
}
return codec;
}
void TextFileEditor::updateCodec()
{
}
void TextFileEditor::cursorPositionChanged()
{
const int line = m_view->currentRow() + 1;
const int column = m_view->currentColumn() + 1;
const QString text = QString(" %1 x %2 ").arg(line, 3, 10, QLatin1Char('0')).arg(column, 3, 10, QLatin1Char('0'));
emit positionChanged(text);
_move_timer->start();
}
void TextFileEditor::updateContext()
{
_move_timer->stop();
_context.setFilename(lastFileName());
CodeCompletion::Pool::self()->updateContext(this, _context);
}
/*!
* Return the EndOfLine of the document. This can't be modified.
* A newly created editor is in platform end of line type. Saving a file converte the
* EndOfLine to the platform behavour.
*/
TextFileEditor::EndOfLineType TextFileEditor::eol() const
{
return m_eol;
}
/*! Auto indent all the document (named Pretty Print). */
bool TextFileEditor::autoIndent()
{
ErrorManager::self()->addMessage(lastFileName(), -1, QtWarningMsg, tr("Can't indent this type of document"), QStringList());
return false;
}
/*! Call the completer of the text on the current position of the cursor, if possible. */
void TextFileEditor::complete()
{
if (_completer)
{
_completer->complete(false);
}
}
void TextFileEditor::contextMenuEvent(QContextMenuEvent * contextMenuEvent)
{
QScopedPointer<QMenu> menu(new QMenu(m_view));
foreach(XinxAction::MenuItem * item, XinxAction::ActionManager::self()->popup())
{
item->updateActionState();
menu->addAction(item->action());
}
menu->exec(contextMenuEvent->globalPos());
}
ContentView3::NodePtr TextFileEditor::localNodeOfWord(const ContentView3::NodePtr & node, const QString & word)
{
foreach(ContentView3::NodePtr child, node->childs())
{
if (child->name() == word)
{
return child;
}
else
{
ContentView3::NodePtr recursiveNode = localNodeOfWord(child, word);
if (recursiveNode)
return recursiveNode;
}
}
return ContentView3::NodePtr();
}
ContentView3::NodePtr TextFileEditor::globalNodeOfWord(const QString & word)
{
ContentView3::NodePtr node = localNodeOfWord(_file->rootNode().dynamicCast<ContentView3::Node> (), word);
if (node.isNull() && project())
{
foreach(const ContentView3::FilePtr & import, project()->cache()->importOf(_file))
{
node = localNodeOfWord(import->rootNode().dynamicCast<ContentView3::Node> (), word);
if (node)
{
break;
}
}
}
return node;
}
void TextFileEditor::searchWord(const QString & word)
{
ContentView3::NodePtr n = globalNodeOfWord(word);
if (n)
{
const QString filename = n->filename();
emit open(filename, n->line());
return; /* Founded */
}
QMessageBox::information(this, tr("Search Word"), tr("Word %1 not found").arg(word));
}
/*!
* \brief Indicate if the snipet must be show or not in the completion list
* \since 0.9.1.0
*
* Used by CodeCompletion::SnipetContextParser to know if snipet must be added
* This methode must subclassed, if the return value must be different.
*
* By default this method return always true.
*/
bool TextFileEditor::isSnipetMustBeShow() const
{
return true;
}
void TextFileEditor::serialize(XinxSession::SessionEditor * data, bool content)
{
AbstractEditor::serialize(data, content);
data->writeProperty("Position", QVariant(m_view->textCursor().position()));
if (content && isModified())
{
data->writeProperty("Content", QVariant(m_view->toPlainText()));
}
int i = 0;
foreach(int line, m_view->listOfBookmark())
{
data->writeProperty(QString("Bookmark_%1").arg(i++), QVariant(line));
}
data->writeProperty("BookmarkCount", QVariant(i));
}
void TextFileEditor::deserialize(XinxSession::SessionEditor * data)
{
AbstractEditor::deserialize(data);
int position = 0;
QString text;
position = data->readProperty("Position") .toInt();
text = data->readProperty("Content").toString();
if (! text.isEmpty())
{
m_view->setPlainText(text);
}
else
{
if (! lastFileName().isEmpty())
loadFromFile(lastFileName());
}
m_view->setFilename(lastFileName());
int bc = data->readProperty("BookmarkCount").toInt();
for (int i = 0 ; i < bc; i++)
{
m_bookmarkInterface->setBookmark(data->readProperty(QString("Bookmark_%1").arg(i)).toInt(), true);
}
QDocumentCursor tc(m_view->editor()->document());
tc.movePosition(position, QDocumentCursor::Right);
m_view->setTextCursor(tc);
}
bool TextFileEditor::canCopy()
{
return m_view->textCursor().hasSelection();
}
bool TextFileEditor::canPaste()
{
return m_view->canPaste();
}
bool TextFileEditor::canUndo()
{
return m_view->editor()->canUndo();
}
bool TextFileEditor::canRedo()
{
return m_view->editor()->canRedo();
}
void TextFileEditor::undo()
{
m_view->editor()->undo();
}
void TextFileEditor::redo()
{
m_view->editor()->redo();
}
void TextFileEditor::cut()
{
m_view->editor()->cut();
}
void TextFileEditor::copy()
{
m_view->editor()->copy();
}
void TextFileEditor::paste()
{
m_view->editor()->paste();
}