Files
create/src/Gui/EditorView.cpp
wmayer d5921e08ec fix (Qt) issues found by clang's clazy tool:
+ -Wclazy-incorrect-emit
+ -Wclazy-strict-iterators
+ -Wclazy-overloaded-signal
+ -Wclazy-qstring-arg
+ -Wclazy-unused-non-trivial-variable
+ -Wclazy-container-anti-pattern
+ -Wclazy-range-loop-reference
+ -Wclazy-const-signal-or-slot
+ -Wclazy-detaching-temporary
+ -Wclazy-qfileinfo-exists
2022-06-29 21:00:54 +02:00

851 lines
24 KiB
C++

/***************************************************************************
* Copyright (c) 2007 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <QApplication>
# include <QCheckBox>
# include <QClipboard>
# include <QDateTime>
# include <QHBoxLayout>
# include <QVBoxLayout>
# include <QLineEdit>
# include <QMessageBox>
# include <QPrinter>
# include <QPrintDialog>
# include <QPlainTextEdit>
# include <QPrintPreviewDialog>
# include <QSpacerItem>
# include <QStyle>
# include <QTextCodec>
# include <QTextCursor>
# include <QTextDocument>
# include <QTextStream>
# include <QTimer>
# include <QToolButton>
#endif
#include "EditorView.h"
#include "Application.h"
#include "FileDialog.h"
#include "Macro.h"
#include "MainWindow.h"
#include "PythonEditor.h"
#include <Base/Exception.h>
#include <Base/Interpreter.h>
#include <Base/Parameter.h>
using namespace Gui;
namespace Gui {
class EditorViewP {
public:
QPlainTextEdit* textEdit;
SearchBar* searchBar;
QString fileName;
EditorView::DisplayName displayName;
QTimer* activityTimer;
uint timeStamp;
bool lock;
bool aboutToClose;
QStringList undos;
QStringList redos;
};
}
// -------------------------------------------------------
/* TRANSLATOR Gui::EditorView */
TYPESYSTEM_SOURCE_ABSTRACT(Gui::EditorView, Gui::MDIView)
/**
* Constructs a EditorView which is a child of 'parent', with the
* name 'name'.
*/
EditorView::EditorView(QPlainTextEdit* editor, QWidget* parent)
: MDIView(nullptr,parent,Qt::WindowFlags()), WindowParameter( "Editor" )
{
d = new EditorViewP;
d->lock = false;
d->aboutToClose = false;
d->displayName = EditorView::FullName;
// create the editor first
d->textEdit = editor;
d->textEdit->setLineWrapMode(QPlainTextEdit::NoWrap);
d->searchBar = new SearchBar();
d->searchBar->setEditor(editor);
// update editor actions on request
Gui::MainWindow* mw = Gui::getMainWindow();
connect(editor, SIGNAL(undoAvailable(bool)), mw, SLOT(updateEditorActions()));
connect(editor, SIGNAL(redoAvailable(bool)), mw, SLOT(updateEditorActions()));
connect(editor, SIGNAL(copyAvailable(bool)), mw, SLOT(updateEditorActions()));
connect(editor, SIGNAL(showSearchBar()), d->searchBar, SLOT(activate()));
connect(editor, SIGNAL(findNext()), d->searchBar, SLOT(findNext()));
connect(editor, SIGNAL(findPrevious()), d->searchBar, SLOT(findPrevious()));
// Create the layout containing the workspace and a tab bar
QFrame* hbox = new QFrame(this);
hbox->setFrameShape(QFrame::StyledPanel);
hbox->setFrameShadow(QFrame::Sunken);
QVBoxLayout* layout = new QVBoxLayout();
layout->setMargin(1);
layout->addWidget(d->textEdit);
layout->addWidget(d->searchBar);
d->textEdit->setParent(hbox);
d->searchBar->setParent(hbox);
hbox->setLayout(layout);
setCentralWidget(hbox);
setCurrentFileName(QString());
d->textEdit->setFocus();
setWindowIcon(d->textEdit->windowIcon());
ParameterGrp::handle hPrefGrp = getWindowParameter();
hPrefGrp->Attach( this );
hPrefGrp->NotifyAll();
d->activityTimer = new QTimer(this);
connect(d->activityTimer, SIGNAL(timeout()),
this, SLOT(checkTimestamp()) );
connect(d->textEdit->document(), SIGNAL(modificationChanged(bool)),
this, SLOT(setWindowModified(bool)));
connect(d->textEdit->document(), SIGNAL(undoAvailable(bool)),
this, SLOT(undoAvailable(bool)));
connect(d->textEdit->document(), SIGNAL(redoAvailable(bool)),
this, SLOT(redoAvailable(bool)));
connect(d->textEdit->document(), SIGNAL(contentsChange(int, int, int)),
this, SLOT(contentsChange(int, int, int)));
}
/** Destroys the object and frees any allocated resources */
EditorView::~EditorView()
{
d->activityTimer->stop();
delete d->activityTimer;
delete d;
getWindowParameter()->Detach( this );
}
QPlainTextEdit* EditorView::getEditor() const
{
return d->textEdit;
}
void EditorView::showEvent(QShowEvent* event)
{
Gui::MainWindow* mw = Gui::getMainWindow();
mw->updateEditorActions();
MDIView::showEvent(event);
}
void EditorView::hideEvent(QHideEvent* event)
{
MDIView::hideEvent(event);
}
void EditorView::closeEvent(QCloseEvent* event)
{
MDIView::closeEvent(event);
if (event->isAccepted()) {
d->aboutToClose = true;
Gui::MainWindow* mw = Gui::getMainWindow();
mw->updateEditorActions();
}
}
void EditorView::OnChange(Base::Subject<const char*> &rCaller,const char* rcReason)
{
Q_UNUSED(rCaller);
ParameterGrp::handle hPrefGrp = getWindowParameter();
if (strcmp(rcReason, "EnableLineNumber") == 0) {
//bool show = hPrefGrp->GetBool( "EnableLineNumber", true );
}
}
void EditorView::checkTimestamp()
{
QFileInfo fi(d->fileName);
uint timeStamp = fi.lastModified().toTime_t();
if (timeStamp != d->timeStamp) {
switch( QMessageBox::question( this, tr("Modified file"),
tr("%1.\n\nThis has been modified outside of the source editor. Do you want to reload it?").arg(d->fileName),
QMessageBox::Yes|QMessageBox::Default, QMessageBox::No|QMessageBox::Escape) )
{
case QMessageBox::Yes:
// updates time stamp and timer
open( d->fileName );
return;
case QMessageBox::No:
d->timeStamp = timeStamp;
break;
}
}
d->activityTimer->setSingleShot(true);
d->activityTimer->start(3000);
}
/**
* Runs the action specified by \a pMsg.
*/
bool EditorView::onMsg(const char* pMsg,const char** /*ppReturn*/)
{
// don't allow any actions if the editor is being closed
if (d->aboutToClose)
return false;
if (strcmp(pMsg, "Save") == 0) {
saveFile();
return true;
}
else if (strcmp(pMsg, "SaveAs") == 0) {
saveAs();
return true;
}
else if (strcmp(pMsg, "Cut") == 0) {
cut();
return true;
}
else if (strcmp(pMsg, "Copy") == 0) {
copy();
return true;
}
else if (strcmp(pMsg, "Paste") == 0) {
paste();
return true;
}
else if (strcmp(pMsg, "Undo") == 0) {
undo();
return true;
}
else if (strcmp(pMsg, "Redo") == 0) {
redo();
return true;
}
else if (strcmp(pMsg, "ViewFit") == 0) {
// just ignore this
return true;
}
return false;
}
/**
* Checks if the action \a pMsg is available. This is for enabling/disabling
* the corresponding buttons or menu items for this action.
*/
bool EditorView::onHasMsg(const char* pMsg) const
{
// don't allow any actions if the editor is being closed
if (d->aboutToClose)
return false;
if (strcmp(pMsg, "Run") == 0)
return true;
if (strcmp(pMsg, "DebugStart") == 0)
return true;
if (strcmp(pMsg, "DebugStop") == 0)
return true;
if (strcmp(pMsg, "SaveAs") == 0)
return true;
if (strcmp(pMsg, "Print") == 0)
return true;
if (strcmp(pMsg, "PrintPreview") == 0)
return true;
if (strcmp(pMsg, "PrintPdf") == 0)
return true;
if (strcmp(pMsg, "Save") == 0) {
return d->textEdit->document()->isModified();
}
else if (strcmp(pMsg, "Cut") == 0) {
bool canWrite = !d->textEdit->isReadOnly();
return (canWrite && (d->textEdit->textCursor().hasSelection()));
}
else if (strcmp(pMsg, "Copy") == 0) {
return ( d->textEdit->textCursor().hasSelection() );
}
else if (strcmp(pMsg, "Paste") == 0) {
QClipboard *cb = QApplication::clipboard();
QString text;
// Copy text from the clipboard (paste)
text = cb->text();
bool canWrite = !d->textEdit->isReadOnly();
return ( !text.isEmpty() && canWrite );
}
else if (strcmp(pMsg, "Undo") == 0) {
return d->textEdit->document()->isUndoAvailable ();
}
else if (strcmp(pMsg, "Redo") == 0) {
return d->textEdit->document()->isRedoAvailable ();
}
return false;
}
/** Checking on close state. */
bool EditorView::canClose(void)
{
if ( !d->textEdit->document()->isModified() )
return true;
this->setFocus(); // raises the view to front
switch( QMessageBox::question(this, tr("Unsaved document"),
tr("The document has been modified.\n"
"Do you want to save your changes?"),
QMessageBox::Yes|QMessageBox::Default, QMessageBox::No,
QMessageBox::Cancel|QMessageBox::Escape))
{
case QMessageBox::Yes:
return saveFile();
case QMessageBox::No:
return true;
case QMessageBox::Cancel:
return false;
default:
return false;
}
}
void EditorView::setDisplayName(EditorView::DisplayName type)
{
d->displayName = type;
}
/**
* Saves the content of the editor to a file specified by the appearing file dialog.
*/
bool EditorView::saveAs(void)
{
QString fn = FileDialog::getSaveFileName(this, QObject::tr("Save Macro"),
QString(), QString::fromLatin1("%1 (*.FCMacro);;Python (*.py)").arg(tr("FreeCAD macro")));
if (fn.isEmpty())
return false;
setCurrentFileName(fn);
return saveFile();
}
/**
* Opens the file \a fileName.
*/
bool EditorView::open(const QString& fileName)
{
if (!QFile::exists(fileName))
return false;
QFile file(fileName);
if (!file.open(QFile::ReadOnly))
return false;
d->lock = true;
d->textEdit->setPlainText(QString::fromUtf8(file.readAll()));
d->lock = false;
d->undos.clear();
d->redos.clear();
file.close();
QFileInfo fi(fileName);
d->timeStamp = fi.lastModified().toTime_t();
d->activityTimer->setSingleShot(true);
d->activityTimer->start(3000);
setCurrentFileName(fileName);
return true;
}
/**
* Copies the selected text to the clipboard and deletes it from the text edit.
* If there is no selected text nothing happens.
*/
void EditorView::cut(void)
{
d->textEdit->cut();
}
/**
* Copies any selected text to the clipboard.
*/
void EditorView::copy(void)
{
d->textEdit->copy();
}
/**
* Pastes the text from the clipboard into the text edit at the current cursor position.
* If there is no text in the clipboard nothing happens.
*/
void EditorView::paste(void)
{
d->textEdit->paste();
}
/**
* Undoes the last operation.
* If there is no operation to undo, i.e. there is no undo step in the undo/redo history, nothing happens.
*/
void EditorView::undo(void)
{
d->lock = true;
if (!d->undos.isEmpty()) {
d->redos << d->undos.back();
d->undos.pop_back();
}
d->textEdit->document()->undo();
d->lock = false;
}
/**
* Redoes the last operation.
* If there is no operation to undo, i.e. there is no undo step in the undo/redo history, nothing happens.
*/
void EditorView::redo(void)
{
d->lock = true;
if (!d->redos.isEmpty()) {
d->undos << d->redos.back();
d->redos.pop_back();
}
d->textEdit->document()->redo();
d->lock = false;
}
/**
* Shows the printer dialog.
*/
void EditorView::print()
{
QPrinter printer(QPrinter::ScreenResolution);
printer.setFullPage(true);
QPrintDialog dlg(&printer, this);
if (dlg.exec() == QDialog::Accepted) {
d->textEdit->document()->print(&printer);
}
}
void EditorView::printPreview()
{
QPrinter printer(QPrinter::ScreenResolution);
QPrintPreviewDialog dlg(&printer, this);
connect(&dlg, SIGNAL(paintRequested (QPrinter *)),
this, SLOT(print(QPrinter *)));
dlg.exec();
}
void EditorView::print(QPrinter* printer)
{
d->textEdit->document()->print(printer);
}
/**
* Prints the document into a Pdf file.
*/
void EditorView::printPdf()
{
QString filename = FileDialog::getSaveFileName(this, tr("Export PDF"), QString(),
QString::fromLatin1("%1 (*.pdf)").arg(tr("PDF file")));
if (!filename.isEmpty()) {
QPrinter printer(QPrinter::ScreenResolution);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName(filename);
d->textEdit->document()->print(&printer);
}
}
void EditorView::setCurrentFileName(const QString &fileName)
{
d->fileName = fileName;
Q_EMIT changeFileName(d->fileName);
d->textEdit->document()->setModified(false);
QString name;
QFileInfo fi(fileName);
switch (d->displayName) {
case FullName:
name = fileName;
break;
case FileName:
name = fi.fileName();
break;
case BaseName:
name = fi.baseName();
break;
}
QString shownName;
if (fileName.isEmpty())
shownName = tr("untitled[*]");
else
shownName = QString::fromLatin1("%1[*]").arg(name);
shownName += tr(" - Editor");
setWindowTitle(shownName);
setWindowModified(false);
}
QString EditorView::fileName() const
{
return d->fileName;
}
/**
* Saves the contents to a file.
*/
bool EditorView::saveFile()
{
if (d->fileName.isEmpty())
return saveAs();
QFile file(d->fileName);
if (!file.open(QFile::WriteOnly))
return false;
QTextStream ts(&file);
ts.setCodec(QTextCodec::codecForName("UTF-8"));
ts << d->textEdit->document()->toPlainText();
file.close();
d->textEdit->document()->setModified(false);
QFileInfo fi(d->fileName);
d->timeStamp = fi.lastModified().toTime_t();
return true;
}
void EditorView::undoAvailable(bool undo)
{
if (!undo)
d->undos.clear();
}
void EditorView::redoAvailable(bool redo)
{
if (!redo)
d->redos.clear();
}
void EditorView::contentsChange(int position, int charsRemoved, int charsAdded)
{
Q_UNUSED(position);
if (d->lock)
return;
if (charsRemoved > 0 && charsAdded > 0)
return; // syntax highlighting
else if (charsRemoved > 0)
d->undos << tr("%1 chars removed").arg(charsRemoved);
else if (charsAdded > 0)
d->undos << tr("%1 chars added").arg(charsAdded);
else
d->undos << tr("Formatted");
d->redos.clear();
}
/**
* Get the undo history.
*/
QStringList EditorView::undoActions() const
{
return d->undos;
}
/**
* Get the redo history.
*/
QStringList EditorView::redoActions() const
{
return d->redos;;
}
void EditorView::focusInEvent (QFocusEvent *)
{
d->textEdit->setFocus();
}
// ---------------------------------------------------------
TYPESYSTEM_SOURCE_ABSTRACT(Gui::PythonEditorView, Gui::EditorView)
PythonEditorView::PythonEditorView(PythonEditor* editor, QWidget* parent)
: EditorView(editor, parent), _pye(editor)
{
connect(this, SIGNAL(changeFileName(const QString&)),
editor, SLOT(setFileName(const QString&)));
}
PythonEditorView::~PythonEditorView()
{
}
/**
* Runs the action specified by \a pMsg.
*/
bool PythonEditorView::onMsg(const char* pMsg,const char** ppReturn)
{
if (strcmp(pMsg,"Run")==0) {
executeScript();
return true;
}
else if (strcmp(pMsg,"StartDebug")==0) {
QTimer::singleShot(300, this, SLOT(startDebug()));
return true;
}
else if (strcmp(pMsg,"ToggleBreakpoint")==0) {
toggleBreakpoint();
return true;
}
return EditorView::onMsg(pMsg, ppReturn);
}
/**
* Checks if the action \a pMsg is available. This is for enabling/disabling
* the corresponding buttons or menu items for this action.
*/
bool PythonEditorView::onHasMsg(const char* pMsg) const
{
if (strcmp(pMsg,"Run")==0)
return true;
if (strcmp(pMsg,"StartDebug")==0)
return true;
if (strcmp(pMsg,"ToggleBreakpoint")==0)
return true;
return EditorView::onHasMsg(pMsg);
}
/**
* Runs the opened script in the macro manager.
*/
void PythonEditorView::executeScript()
{
// always save the macro when it is modified
if (EditorView::onHasMsg("Save"))
EditorView::onMsg("Save", nullptr);
try {
Application::Instance->macroManager()->run(Gui::MacroManager::File,fileName().toUtf8());
}
catch (const Base::SystemExitException&) {
// handle SystemExit exceptions
Base::PyGILStateLocker locker;
Base::PyException e;
e.ReportException();
}
}
void PythonEditorView::startDebug()
{
_pye->startDebug();
}
void PythonEditorView::toggleBreakpoint()
{
_pye->toggleBreakpoint();
}
void PythonEditorView::showDebugMarker(int line)
{
_pye->showDebugMarker(line);
}
void PythonEditorView::hideDebugMarker()
{
_pye->hideDebugMarker();
}
// ----------------------------------------------------------------------------
SearchBar::SearchBar(QWidget* parent)
: QWidget(parent)
, textEditor(nullptr)
{
horizontalLayout = new QHBoxLayout(this);
horizontalLayout->setSpacing(3);
closeButton = new QToolButton(this);
closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton));
closeButton->setAutoRaise(true);
connect(closeButton, &QToolButton::clicked, this, &SearchBar::deactivate);
horizontalLayout->addWidget(closeButton);
searchText = new QLineEdit(this);
searchText->setClearButtonEnabled(true);
horizontalLayout->addWidget(searchText);
connect(searchText, &QLineEdit::returnPressed, this, &SearchBar::findNext);
connect(searchText, &QLineEdit::textChanged, this, &SearchBar::findCurrent);
connect(searchText, &QLineEdit::textChanged, this, &SearchBar::updateButtons);
prevButton = new QToolButton(this);
prevButton->setIcon(style()->standardIcon(QStyle::SP_ArrowBack));
prevButton->setAutoRaise(true);
prevButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
horizontalLayout->addWidget(prevButton);
connect(prevButton, &QToolButton::clicked, this, &SearchBar::findPrevious);
nextButton = new QToolButton(this);
nextButton->setIcon(style()->standardIcon(QStyle::SP_ArrowForward));
nextButton->setAutoRaise(true);
nextButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
horizontalLayout->addWidget(nextButton);
connect(nextButton, &QToolButton::clicked, this, &SearchBar::findNext);
matchCase = new QCheckBox(this);
horizontalLayout->addWidget(matchCase);
connect(matchCase, &QCheckBox::toggled, this, &SearchBar::findCurrent);
matchWord = new QCheckBox(this);
horizontalLayout->addWidget(matchWord);
connect(matchWord, &QCheckBox::toggled, this, &SearchBar::findCurrent);
horizontalSpacer = new QSpacerItem(192, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout->addItem(horizontalSpacer);
retranslateUi();
setMinimumWidth(minimumSizeHint().width());
updateButtons();
hide();
}
void SearchBar::setEditor(QPlainTextEdit* textEdit)
{
textEditor = textEdit;
}
void SearchBar::keyPressEvent(QKeyEvent* event)
{
if (event->key() == Qt::Key_Escape) {
hide();
return;
}
QWidget::keyPressEvent(event);
}
void SearchBar::retranslateUi()
{
prevButton->setText(tr("Previous"));
nextButton->setText(tr("Next"));
matchCase->setText(tr("Case sensitive"));
matchWord->setText(tr("Whole words"));
}
void SearchBar::activate()
{
show();
searchText->selectAll();
searchText->setFocus(Qt::ShortcutFocusReason);
}
void SearchBar::deactivate()
{
if (textEditor)
textEditor->setFocus();
hide();
}
void SearchBar::findPrevious()
{
findText(true, false, searchText->text());
}
void SearchBar::findNext()
{
findText(true, true, searchText->text());
}
void SearchBar::findCurrent()
{
findText(false, true, searchText->text());
}
void SearchBar::findText(bool skip, bool next, const QString& str)
{
if (!textEditor)
return;
QTextCursor cursor = textEditor->textCursor();
QTextDocument *doc = textEditor->document();
if (!doc || cursor.isNull())
return;
if (cursor.hasSelection())
cursor.setPosition((skip && next) ? cursor.position() : cursor.anchor());
bool found = true;
QTextCursor newCursor = cursor;
if (!str.isEmpty()) {
QTextDocument::FindFlags options;
if (!next)
options |= QTextDocument::FindBackward;
if (matchCase->isChecked())
options |= QTextDocument::FindCaseSensitively;
if (matchWord->isChecked())
options |= QTextDocument::FindWholeWords;
newCursor = doc->find(str, cursor, options);
if (newCursor.isNull()) {
QTextCursor ac(doc);
ac.movePosition(options & QTextDocument::FindBackward ? QTextCursor::End : QTextCursor::Start);
newCursor = doc->find(str, ac, options);
if (newCursor.isNull()) {
found = false;
newCursor = cursor;
}
}
}
if (!isVisible())
show();
textEditor->setTextCursor(newCursor);
QString styleSheet;
if (!found) {
styleSheet = QString::fromLatin1(
" QLineEdit {\n"
" background-color: rgb(221,144,161);\n"
" }\n"
);
}
searchText->setStyleSheet(styleSheet);
}
void SearchBar::updateButtons()
{
bool empty = searchText->text().isEmpty();
prevButton->setDisabled(empty);
nextButton->setDisabled(empty);
}
void SearchBar::changeEvent(QEvent* event)
{
if (event->type() == QEvent::LanguageChange) {
retranslateUi();
}
QWidget::changeEvent(event);
}
#include "moc_EditorView.cpp"