File indexing completed on 2024-05-19 04:48:08
0001 /**************************************************************************** 0002 * * 0003 ** Copyright (C) 2017 The Qt Company Ltd. 0004 ** Contact: https://www.qt.io/licensing/ 0005 ** 0006 ** This file is part of the examples of the Qt Toolkit. 0007 ** 0008 ** $QT_BEGIN_LICENSE:BSD$ 0009 ** Commercial License Usage 0010 ** Licensees holding valid commercial Qt licenses may use this file in 0011 ** accordance with the commercial license agreement provided with the 0012 ** Software or, alternatively, in accordance with the terms contained in 0013 ** a written agreement between you and The Qt Company. For licensing terms 0014 ** and conditions see https://www.qt.io/terms-conditions. For further 0015 ** information use the contact form at https://www.qt.io/contact-us. 0016 ** 0017 ** BSD License Usage 0018 ** Alternatively, you may use this file under the terms of the BSD license 0019 ** as follows: 0020 ** 0021 ** "Redistribution and use in source and binary forms, with or without 0022 ** modification, are permitted provided that the following conditions are 0023 ** met: 0024 ** * Redistributions of source code must retain the above copyright 0025 ** notice, this list of conditions and the following disclaimer. 0026 ** * Redistributions in binary form must reproduce the above copyright 0027 ** notice, this list of conditions and the following disclaimer in 0028 ** the documentation and/or other materials provided with the 0029 ** distribution. 0030 ** * Neither the name of The Qt Company Ltd nor the names of its 0031 ** contributors may be used to endorse or promote products derived 0032 ** from this software without specific prior written permission. 0033 ** 0034 ** 0035 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 0036 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 0037 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 0038 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 0039 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 0040 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 0041 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 0042 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 0043 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 0044 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 0045 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 0046 ** 0047 ** $QT_END_LICENSE$ 0048 ** 0049 ****************************************************************************/ 0050 0051 #include "documenthandler.h" 0052 0053 #if defined Q_OS_MACOS || defined Q_OS_WIN 0054 #include <KF5/KI18n/KLocalizedString> 0055 #else 0056 #include <KLocalizedString> 0057 #endif 0058 0059 #include <QAbstractTextDocumentLayout> 0060 #include <QDebug> 0061 #include <QFile> 0062 #include <QFileInfo> 0063 #include <QFileSelector> 0064 #include <QFileSystemWatcher> 0065 #include <QQmlFile> 0066 #include <QQmlFileSelector> 0067 #include <QQuickTextDocument> 0068 #include <QTextCharFormat> 0069 #include <QTextCodec> 0070 #include <QTextDocument> 0071 #include <QTextDocumentWriter> 0072 #include <QUrl> 0073 0074 #include <MauiKit3/Core/fmh.h> 0075 0076 #if defined Q_OS_MACOS || defined Q_OS_WIN32 0077 #include <KF5/KSyntaxHighlighting/Definition> 0078 #include <KF5/KSyntaxHighlighting/Repository> 0079 #include <KF5/KSyntaxHighlighting/SyntaxHighlighter> 0080 #include <KF5/KSyntaxHighlighting/Theme> 0081 #else 0082 #include <KSyntaxHighlighting/Definition> 0083 #include <KSyntaxHighlighting/Repository> 0084 #include <KSyntaxHighlighting/SyntaxHighlighter> 0085 #include <KSyntaxHighlighting/Theme> 0086 #endif 0087 0088 #define AUTOSAVE_TIMEOUT 5000 0089 0090 /** 0091 * Global Variables 0092 */ 0093 KSyntaxHighlighting::Repository *DocumentHandler::m_repository = nullptr; 0094 int DocumentHandler::m_instanceCount = 0; 0095 0096 Alerts::Alerts(QObject *parent) 0097 : QAbstractListModel(parent) 0098 { 0099 } 0100 0101 Alerts::~Alerts() 0102 { 0103 qDebug() << "REMOVING ALL DOCUMENTS ALERTS" << this->m_alerts.size(); 0104 for (auto *alert : qAsConst(m_alerts)) { 0105 delete alert; 0106 alert = nullptr; 0107 } 0108 } 0109 0110 QVariant Alerts::data(const QModelIndex &index, int role) const 0111 { 0112 if (role == ROLES::ALERT) 0113 return QVariant::fromValue(this->m_alerts.at(index.row())); 0114 0115 return QVariant(); 0116 } 0117 0118 int Alerts::rowCount(const QModelIndex &parent) const 0119 { 0120 if (parent.isValid()) 0121 return 0; 0122 0123 return this->m_alerts.count(); 0124 } 0125 0126 QHash<int, QByteArray> Alerts::roleNames() const 0127 { 0128 return {{ROLES::ALERT, "alert"}}; 0129 } 0130 0131 bool Alerts::contains(DocumentAlert *const alert) 0132 { 0133 for (const auto &alert_ : qAsConst(m_alerts)) { 0134 if (alert_->getId() == alert->getId()) 0135 return true; 0136 } 0137 0138 return false; 0139 } 0140 0141 void Alerts::append(DocumentAlert *alert) 0142 { 0143 if (this->contains(alert)) 0144 return; 0145 0146 const auto index = this->rowCount(); 0147 beginInsertRows(QModelIndex(), index, index); 0148 0149 // watch out for when the alert is done: such as when an action is triggered 0150 connect(alert, &DocumentAlert::done, [this](int index) { 0151 this->beginRemoveRows(QModelIndex(), index, index); 0152 auto item = this->m_alerts.takeAt(index); 0153 if (item) { 0154 item->deleteLater(); 0155 item = nullptr; 0156 } 0157 this->endRemoveRows(); 0158 }); 0159 0160 alert->setIndex(index); 0161 this->m_alerts << alert; 0162 endInsertRows(); 0163 } 0164 0165 void FileLoader::loadFile(const QUrl &url) 0166 { 0167 if (FMH::fileExists(url)) { 0168 QFile file(url.toLocalFile()); 0169 if (file.open(QFile::ReadOnly)) { 0170 const auto array = file.readAll(); 0171 QTextCodec *codec = QTextDocumentWriter(url.toLocalFile()).codec(); 0172 Q_EMIT this->fileReady(codec->toUnicode(array), url); 0173 } 0174 } 0175 } 0176 0177 DocumentAlert *DocumentHandler::externallyModifiedAlert() 0178 { 0179 auto alert = new DocumentAlert(i18nd("mauikittexteditor","File changed externally"), i18nd("mauikittexteditor","You can reload the file or save your changes now"), DocumentAlert::WARNING_LEVEL, Alerts::MODIFIED); 0180 0181 const auto reloadAction = [this]() { 0182 Q_EMIT this->loadFile(this->fileUrl()); 0183 }; 0184 0185 const auto autoReloadAction = [this]() { 0186 this->setAutoReload(true); 0187 Q_EMIT this->loadFile(this->fileUrl()); 0188 }; 0189 0190 alert->setActions({{i18nd("mauikittexteditor","Reload"), reloadAction}, {i18nd("mauikittexteditor","Auto Reload"), autoReloadAction}, {i18nd("mauikittexteditor","Ignore"), []() {}}}); 0191 return alert; 0192 } 0193 0194 DocumentAlert *DocumentHandler::canNotSaveAlert(const QString &details) 0195 { 0196 auto alert = new DocumentAlert(i18nd("mauikittexteditor","File can not be saved"), details, DocumentAlert::DANGER_LEVEL, Alerts::SAVE_ERROR); 0197 0198 alert->setActions({{i18nd("mauikittexteditor","Ignore"), []() {}}}); 0199 return alert; 0200 } 0201 0202 DocumentAlert *DocumentHandler::missingAlert() 0203 { 0204 auto alert = new DocumentAlert(i18nd("mauikittexteditor","Your file was removed"), i18nd("mauikittexteditor","This file does not longer exist in your local storage, however you can save it again"), DocumentAlert::DANGER_LEVEL, Alerts::MISSING); 0205 0206 const auto saveAction = [this]() { 0207 this->saveAs(this->fileUrl()); 0208 }; 0209 0210 alert->setActions({{i18nd("mauikittexteditor","Save"), saveAction}}); 0211 return alert; 0212 } 0213 0214 DocumentHandler::DocumentHandler(QObject *parent) 0215 : QObject(parent) 0216 , m_document(nullptr) 0217 , m_watcher(new QFileSystemWatcher(this)) 0218 , m_cursorPosition(-1) 0219 , m_selectionStart(0) 0220 , m_selectionEnd(0) 0221 , m_highlighter(new KSyntaxHighlighting::SyntaxHighlighter(this)) 0222 , m_alerts(new Alerts(this)) 0223 { 0224 ++m_instanceCount; 0225 0226 // start file loader thread implementation 0227 { 0228 FileLoader *m_loader = new FileLoader; 0229 m_loader->moveToThread(&m_worker); 0230 connect(&m_worker, &QThread::finished, m_loader, &QObject::deleteLater); 0231 connect(this, &DocumentHandler::loadFile, m_loader, &FileLoader::loadFile); 0232 connect(m_loader, &FileLoader::fileReady, [this](QString array, QUrl url) { 0233 this->setText(array); 0234 0235 if (this->textDocument()) { 0236 this->textDocument()->setModified(false); 0237 0238 this->isRich = Qt::mightBeRichText(this->text()); 0239 Q_EMIT this->isRichChanged(); 0240 } 0241 0242 Q_EMIT this->loaded(url); 0243 0244 reset(); 0245 }); 0246 m_worker.start(); 0247 } 0248 // end file loader thread implementation 0249 0250 connect(&m_autoSaveTimer, &QTimer::timeout, [this]() { 0251 if (m_autoSave && getModified() && !m_fileUrl.isEmpty()) { 0252 qDebug() << "Autosaving file" << m_fileUrl; 0253 saveAs(m_fileUrl); 0254 m_autoSaveTimer.start(AUTOSAVE_TIMEOUT); 0255 } 0256 }); 0257 0258 if (m_autoSave) 0259 m_autoSaveTimer.start(AUTOSAVE_TIMEOUT); 0260 0261 connect(this, &DocumentHandler::cursorPositionChanged, [this]() { 0262 Q_EMIT this->currentLineIndexChanged(); 0263 }); 0264 0265 connect(this->m_watcher, &QFileSystemWatcher::fileChanged, [this](QString url) { 0266 if (this->fileUrl() == QUrl::fromLocalFile(url)) { 0267 // THE FILE WAS REMOVED 0268 if (!FMH::fileExists(this->fileUrl())) { 0269 this->m_alerts->append(DocumentHandler::missingAlert()); 0270 return; 0271 } 0272 0273 // THE FILE CHANGED BUT STILL EXISTS LOCALLY 0274 if (m_internallyModified) { 0275 m_internallyModified = false; 0276 return; 0277 } 0278 0279 this->setExternallyModified(true); 0280 0281 if (!this->m_autoReload) { 0282 this->m_alerts->append(DocumentHandler::externallyModifiedAlert()); 0283 return; 0284 } 0285 0286 Q_EMIT this->loadFile(this->fileUrl()); 0287 } 0288 }); 0289 } 0290 0291 DocumentHandler::~DocumentHandler() 0292 { 0293 this->m_worker.quit(); 0294 this->m_worker.wait(); 0295 0296 --DocumentHandler::m_instanceCount; 0297 0298 if (!DocumentHandler::m_instanceCount) { 0299 delete DocumentHandler::m_repository; 0300 DocumentHandler::m_repository = nullptr; 0301 } 0302 } 0303 0304 void DocumentHandler::setText(const QString &text) 0305 { 0306 if (text != this->m_text) { 0307 this->m_text = text; 0308 Q_EMIT textChanged(); 0309 } 0310 } 0311 0312 bool DocumentHandler::getAutoReload() const 0313 { 0314 return this->m_autoReload; 0315 } 0316 0317 void DocumentHandler::setAutoReload(const bool &value) 0318 { 0319 if (value == this->m_autoReload) 0320 return; 0321 0322 this->m_autoReload = value; 0323 Q_EMIT this->autoReloadChanged(); 0324 } 0325 0326 bool DocumentHandler::autoSave() const 0327 { 0328 return m_autoSave; 0329 } 0330 0331 void DocumentHandler::setAutoSave(const bool &value) 0332 { 0333 if (m_autoSave == value) 0334 return; 0335 0336 m_autoSave = value; 0337 Q_EMIT autoSaveChanged(); 0338 0339 if (m_autoSave) { 0340 if (!m_autoSaveTimer.isActive()) 0341 m_autoSaveTimer.start(AUTOSAVE_TIMEOUT); 0342 } else 0343 m_autoSaveTimer.stop(); 0344 } 0345 0346 bool DocumentHandler::getModified() const 0347 { 0348 if (auto doc = this->textDocument()) 0349 return doc->isModified(); 0350 0351 return false; 0352 } 0353 0354 bool DocumentHandler::getExternallyModified() const 0355 { 0356 return this->m_externallyModified; 0357 } 0358 0359 void DocumentHandler::setExternallyModified(const bool &value) 0360 { 0361 if (value == this->m_externallyModified) 0362 return; 0363 0364 this->m_externallyModified = value; 0365 Q_EMIT this->externallyModifiedChanged(); 0366 } 0367 0368 void DocumentHandler::setStyle() 0369 { 0370 if (!DocumentHandler::m_repository) 0371 DocumentHandler::m_repository = new KSyntaxHighlighting::Repository(); 0372 0373 qDebug() << "Setting ths tyle" << m_formatName; 0374 if (!m_enableSyntaxHighlighting || m_formatName == "None") { 0375 this->m_highlighter->setDocument(nullptr); 0376 // this->m_highlighter->setTheme(KSyntaxHighlighting::Theme()); 0377 // this->m_highlighter->setDefinition(m_repository->definitionForName( "None" )); 0378 // this->m_highlighter->rehighlight(); 0379 return; 0380 } 0381 0382 qDebug() << "Setting the style for syntax highligthing"; 0383 0384 const auto def = m_repository->definitionForName(this->m_formatName); 0385 if (!def.isValid()) { 0386 qDebug() << "Highliging definition is not valid" << def.name() << def.filePath() << def.author() << m_formatName; 0387 return; 0388 } 0389 0390 if (!m_highlighter->document()) { 0391 this->m_highlighter->setDocument(this->textDocument()); 0392 } 0393 0394 qDebug() << "Highliging definition info" << def.name() << def.filePath() << def.author() << m_formatName; 0395 0396 this->m_highlighter->setDefinition(def); 0397 0398 if (m_theme.isEmpty()) { 0399 const bool isDark = DocumentHandler::isDark(this->m_backgroundColor); 0400 const auto style = DocumentHandler::m_repository->defaultTheme(isDark ? KSyntaxHighlighting::Repository::DarkTheme : KSyntaxHighlighting::Repository::LightTheme); 0401 this->m_highlighter->setTheme(style); 0402 0403 } else { 0404 qDebug() << "Applying theme << " << m_theme << DocumentHandler::m_repository->theme(m_theme).isValid(); 0405 const auto style = DocumentHandler::m_repository->theme(m_theme); 0406 this->m_highlighter->setTheme(style); 0407 this->m_highlighter->rehighlight(); 0408 } 0409 0410 refreshAllBlocks(); 0411 } 0412 0413 void DocumentHandler::refreshAllBlocks() 0414 { 0415 if (textDocument()) { 0416 for (QTextBlock it = textDocument()->begin(); it != textDocument()->end(); it = it.next()) 0417 { 0418 Q_EMIT this->textDocument()->documentLayout()->updateBlock(it); 0419 } 0420 } 0421 } 0422 0423 QString DocumentHandler::formatName() const 0424 { 0425 return this->m_formatName; 0426 } 0427 0428 void DocumentHandler::setFormatName(const QString &formatName) 0429 { 0430 if (this->m_formatName != formatName) { 0431 this->m_formatName = formatName; 0432 Q_EMIT this->formatNameChanged(); 0433 } 0434 0435 this->setStyle(); 0436 } 0437 0438 QColor DocumentHandler::getBackgroundColor() const 0439 { 0440 return this->m_backgroundColor; 0441 } 0442 0443 void DocumentHandler::setBackgroundColor(const QColor &color) 0444 { 0445 if (this->m_backgroundColor == color) 0446 return; 0447 0448 this->m_backgroundColor = color; 0449 Q_EMIT this->backgroundColorChanged(); 0450 0451 if (!DocumentHandler::m_repository) 0452 DocumentHandler::m_repository = new KSyntaxHighlighting::Repository(); 0453 } 0454 0455 Alerts *DocumentHandler::getAlerts() const 0456 { 0457 return this->m_alerts; 0458 } 0459 0460 QQuickTextDocument *DocumentHandler::document() const 0461 { 0462 return m_document; 0463 } 0464 0465 void DocumentHandler::setDocument(QQuickTextDocument *document) 0466 { 0467 this->m_document = document; 0468 Q_EMIT documentChanged(); 0469 0470 if (this->textDocument()) { 0471 this->textDocument()->setModified(false); 0472 connect(this->textDocument(), &QTextDocument::modificationChanged, this, &DocumentHandler::modifiedChanged); 0473 0474 connect(this->textDocument(), &QTextDocument::blockCountChanged, this, &DocumentHandler::lineCountChanged); 0475 0476 // connect(this->textDocument(), &QTextDocument::cursorPositionChanged, [this](const QTextCursor &) 0477 // { 0478 // qDebug() << "Cursors position changed"; 0479 // Q_EMIT currentLineIndexChanged(); 0480 // }); 0481 0482 this->load(m_fileUrl); 0483 0484 QTextOption textOptions = this->textDocument()->defaultTextOption(); 0485 textOptions.setTabStopDistance(m_tabSpace); 0486 textDocument()->setDefaultTextOption(textOptions); 0487 } 0488 } 0489 0490 int DocumentHandler::cursorPosition() const 0491 { 0492 return m_cursorPosition; 0493 } 0494 0495 void DocumentHandler::setCursorPosition(int position) 0496 { 0497 if(m_cursorPosition == position) 0498 { 0499 return; 0500 } 0501 0502 m_cursorPosition = position; 0503 Q_EMIT cursorPositionChanged(); 0504 } 0505 0506 int DocumentHandler::selectionStart() const 0507 { 0508 return m_selectionStart; 0509 } 0510 0511 void DocumentHandler::setSelectionStart(int position) 0512 { 0513 if (position == m_selectionStart) 0514 return; 0515 0516 m_selectionStart = position; 0517 Q_EMIT selectionStartChanged(); 0518 } 0519 0520 int DocumentHandler::selectionEnd() const 0521 { 0522 return m_selectionEnd; 0523 } 0524 0525 void DocumentHandler::setSelectionEnd(int position) 0526 { 0527 if (position == m_selectionEnd) 0528 return; 0529 0530 m_selectionEnd = position; 0531 Q_EMIT selectionEndChanged(); 0532 } 0533 0534 QString DocumentHandler::fontFamily() const 0535 { 0536 QTextCursor cursor = textCursor(); 0537 if (cursor.isNull()) 0538 return QString(); 0539 QTextCharFormat format = cursor.charFormat(); 0540 return format.font().family(); 0541 } 0542 0543 void DocumentHandler::setFontFamily(const QString &family) 0544 { 0545 QTextCharFormat format; 0546 format.setFontFamily(family); 0547 mergeFormatOnWordOrSelection(format); 0548 Q_EMIT fontFamilyChanged(); 0549 } 0550 0551 QColor DocumentHandler::textColor() const 0552 { 0553 QTextCursor cursor = textCursor(); 0554 if (cursor.isNull()) 0555 return QColor(Qt::black); 0556 QTextCharFormat format = cursor.charFormat(); 0557 return format.foreground().color(); 0558 } 0559 0560 void DocumentHandler::setTextColor(const QColor &color) 0561 { 0562 QTextCharFormat format; 0563 format.setForeground(QBrush(color)); 0564 mergeFormatOnWordOrSelection(format); 0565 Q_EMIT textColorChanged(); 0566 } 0567 0568 Qt::Alignment DocumentHandler::alignment() const 0569 { 0570 QTextCursor cursor = textCursor(); 0571 if (cursor.isNull()) 0572 return Qt::AlignLeft; 0573 return textCursor().blockFormat().alignment(); 0574 } 0575 0576 void DocumentHandler::setAlignment(Qt::Alignment alignment) 0577 { 0578 QTextBlockFormat format; 0579 format.setAlignment(alignment); 0580 QTextCursor cursor = textCursor(); 0581 cursor.mergeBlockFormat(format); 0582 Q_EMIT alignmentChanged(); 0583 } 0584 0585 bool DocumentHandler::bold() const 0586 { 0587 QTextCursor cursor = textCursor(); 0588 if (cursor.isNull()) 0589 return false; 0590 return textCursor().charFormat().fontWeight() == QFont::Bold; 0591 } 0592 0593 void DocumentHandler::setBold(bool bold) 0594 { 0595 QTextCharFormat format; 0596 format.setFontWeight(bold ? QFont::Bold : QFont::Normal); 0597 mergeFormatOnWordOrSelection(format); 0598 Q_EMIT boldChanged(); 0599 } 0600 0601 bool DocumentHandler::uppercase() const 0602 { 0603 QTextCursor cursor = textCursor(); 0604 if (cursor.isNull()) 0605 return false; 0606 return textCursor().charFormat().fontCapitalization() == QFont::AllUppercase; 0607 } 0608 0609 void DocumentHandler::setUppercase(bool uppercase) 0610 { 0611 QTextCharFormat format; 0612 format.setFontCapitalization(uppercase ? QFont::AllUppercase : QFont::AllLowercase); 0613 mergeFormatOnWordOrSelection(format); 0614 Q_EMIT uppercaseChanged(); 0615 } 0616 0617 bool DocumentHandler::italic() const 0618 { 0619 QTextCursor cursor = textCursor(); 0620 if (cursor.isNull()) 0621 return false; 0622 return textCursor().charFormat().fontItalic(); 0623 } 0624 0625 void DocumentHandler::setItalic(bool italic) 0626 { 0627 QTextCharFormat format; 0628 format.setFontItalic(italic); 0629 mergeFormatOnWordOrSelection(format); 0630 Q_EMIT italicChanged(); 0631 } 0632 0633 bool DocumentHandler::underline() const 0634 { 0635 QTextCursor cursor = textCursor(); 0636 if (cursor.isNull()) 0637 return false; 0638 return textCursor().charFormat().fontUnderline(); 0639 } 0640 0641 void DocumentHandler::setUnderline(bool underline) 0642 { 0643 QTextCharFormat format; 0644 format.setFontUnderline(underline); 0645 mergeFormatOnWordOrSelection(format); 0646 Q_EMIT underlineChanged(); 0647 } 0648 0649 bool DocumentHandler::getIsRich() const 0650 { 0651 return this->isRich; 0652 } 0653 0654 int DocumentHandler::fontSize() const 0655 { 0656 QTextCursor cursor = textCursor(); 0657 if (cursor.isNull()) 0658 return 0; 0659 QTextCharFormat format = cursor.charFormat(); 0660 return format.font().pointSize(); 0661 } 0662 0663 void DocumentHandler::setFontSize(int size) 0664 { 0665 if (size <= 0) 0666 return; 0667 0668 QTextCursor cursor = textCursor(); 0669 if (cursor.isNull()) 0670 return; 0671 0672 if (!cursor.hasSelection()) 0673 cursor.select(QTextCursor::WordUnderCursor); 0674 0675 if (cursor.charFormat().property(QTextFormat::FontPointSize).toInt() == size) 0676 return; 0677 0678 QTextCharFormat format; 0679 format.setFontPointSize(size); 0680 mergeFormatOnWordOrSelection(format); 0681 Q_EMIT fontSizeChanged(); 0682 } 0683 0684 void DocumentHandler::setTabSpace(qreal value) 0685 { 0686 if (m_tabSpace == value) 0687 return; 0688 0689 m_tabSpace = value; 0690 0691 if (textDocument()) { 0692 QTextOption textOptions = this->textDocument()->defaultTextOption(); 0693 textOptions.setTabStopDistance(m_tabSpace); 0694 textDocument()->setDefaultTextOption(textOptions); 0695 } 0696 0697 Q_EMIT tabSpaceChanged(); 0698 refreshAllBlocks(); 0699 } 0700 0701 qreal DocumentHandler::tabSpace() const 0702 { 0703 return m_tabSpace; 0704 } 0705 0706 QString DocumentHandler::fileName() const 0707 { 0708 const QString filePath = QQmlFile::urlToLocalFileOrQrc(m_fileUrl); 0709 const QString fileName = QFileInfo(filePath).fileName(); 0710 if (fileName.isEmpty()) 0711 return QStringLiteral("untitled.txt"); 0712 return fileName; 0713 } 0714 0715 QString DocumentHandler::fileType() const 0716 { 0717 return QFileInfo(fileName()).suffix(); 0718 } 0719 0720 QUrl DocumentHandler::fileUrl() const 0721 { 0722 return m_fileUrl; 0723 } 0724 0725 void DocumentHandler::setFileUrl(const QUrl &url) 0726 { 0727 if (url == m_fileUrl) 0728 return; 0729 0730 m_fileUrl = url; 0731 0732 load(m_fileUrl); 0733 0734 Q_EMIT fileUrlChanged(); 0735 Q_EMIT fileInfoChanged(); 0736 } 0737 0738 QVariantMap DocumentHandler::fileInfo() const 0739 { 0740 0741 const QFileInfo file(m_fileUrl.toLocalFile()); 0742 if(file.exists()) 0743 { 0744 return QVariantMap(); 0745 } 0746 0747 QVariantMap map = { 0748 {FMH::MODEL_NAME[FMH::MODEL_KEY::LABEL], file.fileName()}, 0749 {FMH::MODEL_NAME[FMH::MODEL_KEY::NAME], file.fileName()} 0750 }; 0751 0752 return map; 0753 } 0754 0755 void DocumentHandler::load(const QUrl &url) 0756 { 0757 qDebug() << "TRYING TO LOAD FILE << " << url << url.isEmpty(); 0758 if (!textDocument()) 0759 return; 0760 0761 if (m_fileUrl.isLocalFile() && !FMH::fileExists(m_fileUrl)) 0762 return; 0763 0764 QQmlEngine *engine = qmlEngine(this); 0765 if (!engine) { 0766 qWarning() << "load() called before DocumentHandler has QQmlEngine"; 0767 return; 0768 } 0769 0770 this->m_watcher->removePaths(this->m_watcher->files()); 0771 this->m_watcher->addPath(m_fileUrl.toLocalFile()); 0772 0773 Q_EMIT this->loadFile(m_fileUrl); 0774 0775 if (m_enableSyntaxHighlighting) { 0776 this->setFormatName(DocumentHandler::getLanguageNameFromFileName(m_fileUrl)); 0777 } 0778 } 0779 0780 void DocumentHandler::saveAs(const QUrl &url) 0781 { 0782 if (url.isEmpty() || !url.isValid()) 0783 return; 0784 0785 QTextDocument *doc = this->textDocument(); 0786 if (!doc) 0787 return; 0788 0789 this->m_internallyModified = true; 0790 0791 // QTextDocumentWriter textWriter(url.toLocalFile()); 0792 // if(!textWriter.write(this->textDocument())) 0793 // { 0794 // Q_EMIT error(i18nd("mauikittexteditor","Cannot save file ")+ url.toString()); 0795 // qWarning() << "can not save file" << textWriter.supportedDocumentFormats() << textWriter.format(); 0796 // this->m_alerts->append(this->canNotSaveAlert(i18nd("mauikittexteditor","Cannot save file ")+ url.toString())); 0797 // return; 0798 // } 0799 0800 const QString filePath = url.toLocalFile(); 0801 const bool isHtml = QFileInfo(filePath).suffix().contains(QLatin1String("html")); 0802 QFile file(filePath); 0803 if (!file.open(QFile::WriteOnly | QFile::Truncate | (isHtml ? QFile::NotOpen : QFile::Text))) { 0804 Q_EMIT error(i18nd("mauikittexteditor","Cannot save: ") + file.errorString()); 0805 this->m_alerts->append(this->canNotSaveAlert(i18nd("mauikittexteditor","Cannot save file ") + file.errorString() + url.toString())); 0806 0807 return; 0808 } 0809 file.write((isHtml ? doc->toHtml() : doc->toPlainText()).toUtf8()); 0810 file.close(); 0811 Q_EMIT fileSaved(); 0812 0813 doc->setModified(false); 0814 0815 if (url == m_fileUrl) 0816 return; 0817 0818 m_fileUrl = url; 0819 Q_EMIT fileUrlChanged(); 0820 } 0821 0822 const QString DocumentHandler::getLanguageNameFromFileName(const QUrl &fileName) 0823 { 0824 if (!DocumentHandler::m_repository) 0825 DocumentHandler::m_repository = new KSyntaxHighlighting::Repository(); 0826 const auto res = DocumentHandler::m_repository->definitionForFileName(fileName.toString()); 0827 0828 return res.isValid() ? res.name() : QString(); 0829 } 0830 0831 const QStringList DocumentHandler::getLanguageNameList() 0832 { 0833 if (!DocumentHandler::m_repository) 0834 m_repository = new KSyntaxHighlighting::Repository(); 0835 0836 const auto definitions = DocumentHandler::m_repository->definitions(); 0837 return std::accumulate(definitions.constBegin(), definitions.constEnd(), QStringList(), [](QStringList &languages, const auto &definition) -> QStringList { 0838 languages.append(definition.name()); 0839 return languages; 0840 }); 0841 } 0842 0843 0844 void DocumentHandler::reset() 0845 { 0846 Q_EMIT fontFamilyChanged(); 0847 Q_EMIT alignmentChanged(); 0848 Q_EMIT boldChanged(); 0849 Q_EMIT italicChanged(); 0850 Q_EMIT underlineChanged(); 0851 Q_EMIT fontSizeChanged(); 0852 Q_EMIT textColorChanged(); 0853 } 0854 0855 QTextCursor DocumentHandler::textCursor() const 0856 { 0857 QTextDocument *doc = textDocument(); 0858 if (!doc) 0859 return QTextCursor(); 0860 0861 QTextCursor cursor = QTextCursor(doc); 0862 if (m_selectionStart != m_selectionEnd) { 0863 cursor.setPosition(m_selectionStart); 0864 cursor.setPosition(m_selectionEnd, QTextCursor::KeepAnchor); 0865 } else { 0866 cursor.setPosition(m_cursorPosition); 0867 } 0868 return cursor; 0869 } 0870 0871 QTextDocument *DocumentHandler::textDocument() const 0872 { 0873 if (!m_document) 0874 return nullptr; 0875 0876 return m_document->textDocument(); 0877 } 0878 0879 void DocumentHandler::mergeFormatOnWordOrSelection(const QTextCharFormat &format) 0880 { 0881 QTextCursor cursor = textCursor(); 0882 if (!cursor.hasSelection()) 0883 cursor.select(QTextCursor::WordUnderCursor); 0884 cursor.mergeCharFormat(format); 0885 } 0886 0887 void DocumentHandler::find(const QString &query,const bool &forward) 0888 { 0889 qDebug() << "Asked to find" << query; 0890 QTextDocument *doc = textDocument(); 0891 0892 if (!doc) { 0893 return; 0894 } 0895 0896 QTextDocument::FindFlags searchFlags; 0897 QTextDocument::FindFlags newFlags = searchFlags; 0898 0899 if (!forward) 0900 { 0901 newFlags = searchFlags | QTextDocument::FindBackward; 0902 } 0903 0904 if (m_findCaseSensitively) 0905 { 0906 newFlags = newFlags | QTextDocument::FindCaseSensitively; 0907 } 0908 0909 if (m_findWholeWords) 0910 { 0911 newFlags = newFlags | QTextDocument::FindWholeWords; 0912 } 0913 0914 QTextCursor start = this->textCursor(); 0915 0916 if(query != m_searchQuery ) 0917 { 0918 start.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor); 0919 m_searchQuery = query; 0920 } 0921 0922 if (!start.isNull() && !start.atEnd()) 0923 { 0924 QTextCursor found = doc->find(m_searchQuery, start, newFlags); 0925 if (found.isNull()) 0926 { 0927 if (!forward) 0928 start.movePosition (QTextCursor::End, QTextCursor::MoveAnchor); 0929 else 0930 start.movePosition (QTextCursor::Start, QTextCursor::MoveAnchor); 0931 0932 this->setCursorPosition(start.position()); 0933 0934 found = doc->find(m_searchQuery, start, newFlags); 0935 } 0936 0937 if (!found.isNull()) 0938 { 0939 // found.movePosition(QTextCursor::WordRight, QTextCursor::MoveAnchor); 0940 setSelectionStart(found.selectionStart()); 0941 setSelectionEnd(found.selectionEnd()); 0942 setCursorPosition(found.position()); 0943 Q_EMIT searchFound(selectionStart(), selectionEnd()); 0944 } 0945 } 0946 } 0947 0948 void DocumentHandler::replace(const QString &query, const QString &value) 0949 { 0950 if(value.isEmpty()) 0951 { 0952 return; 0953 } 0954 0955 if (this->textDocument()) { 0956 0957 if(m_searchQuery.isEmpty() || query != m_searchQuery) 0958 { 0959 find(query); 0960 } 0961 0962 auto cursor = this->textCursor(); 0963 cursor.beginEditBlock(); 0964 cursor.insertText(value); 0965 cursor.endEditBlock(); 0966 0967 find(query); 0968 } 0969 } 0970 0971 void DocumentHandler::replaceAll(const QString &query, const QString &value) 0972 { 0973 QTextDocument *doc = textDocument(); 0974 0975 if (!doc) { 0976 return; 0977 } 0978 0979 QTextCursor newCursor(doc); 0980 newCursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor); 0981 0982 if(newCursor.isNull() || newCursor.atEnd()) 0983 { 0984 return; 0985 } 0986 0987 QTextDocument::FindFlags searchFlags; 0988 QTextDocument::FindFlags newFlags = searchFlags; 0989 0990 if (m_findCaseSensitively) 0991 { 0992 newFlags = searchFlags | QTextDocument::FindCaseSensitively; 0993 } 0994 0995 if (m_findWholeWords) 0996 { 0997 newFlags = searchFlags | QTextDocument::FindWholeWords; 0998 } 0999 1000 while (!newCursor.isNull() && !newCursor.atEnd()) { 1001 newCursor = doc->find(query, newCursor, newFlags); 1002 1003 if (!newCursor.isNull()) { 1004 1005 // newCursor.movePosition(QTextCursor::NoMove, 1006 // QTextCursor::KeepAnchor); 1007 1008 newCursor.beginEditBlock(); 1009 newCursor.insertText(value); 1010 newCursor.endEditBlock(); 1011 1012 } 1013 } 1014 } 1015 1016 bool DocumentHandler::isFoldable(const int &line) const 1017 { 1018 if(!m_highlighter) 1019 return false; 1020 1021 if(auto doc = this->textDocument()) 1022 { 1023 return m_highlighter->startsFoldingRegion(doc->findBlockByLineNumber(line)); 1024 } 1025 1026 return false; 1027 } 1028 1029 bool DocumentHandler::isFolded(const int &line) const 1030 { 1031 if(!m_highlighter) 1032 return false; 1033 1034 if(auto doc = this->textDocument()) 1035 { 1036 auto block = doc->findBlockByLineNumber(line); 1037 1038 if (!block.isValid()) 1039 return false; 1040 1041 const auto nextBlock = block.next(); 1042 1043 if (!nextBlock.isValid()) 1044 return false; 1045 1046 return !nextBlock.isVisible(); 1047 } 1048 1049 return false; 1050 } 1051 1052 void DocumentHandler::toggleFold(const int &line) 1053 { 1054 if(!m_highlighter) 1055 return; 1056 1057 if(auto doc = this->textDocument()) 1058 { 1059 auto startBlock = doc->findBlockByLineNumber(line); 1060 1061 // we also want to fold the last line of the region, therefore the ".next()" 1062 const auto endBlock = 1063 m_highlighter->findFoldingRegionEnd(startBlock).next(); 1064 1065 qDebug() << "Fold line"<< line << startBlock.position() << endBlock.position() << doc->blockCount(); 1066 // fold 1067 auto block = startBlock.next(); 1068 while (block.isValid() && block != endBlock) 1069 { 1070 block.setVisible(false); 1071 block.setLineCount(0); 1072 block = block.next(); 1073 } 1074 1075 1076 for (QTextBlock it = startBlock; it != endBlock; it = it.next()) 1077 { 1078 Q_EMIT this->textDocument()->documentLayout()->updateBlock(it); 1079 } 1080 1081 // redraw document 1082 // doc->markContentsDirty(startBlock.position(), endBlock.position()); 1083 qDebug() << "Fold line"<< line << startBlock.position() << endBlock.position() << doc->blockCount(); 1084 1085 // // update scrollbars 1086 Q_EMIT doc->documentLayout()->documentSizeChanged( 1087 doc->documentLayout()->documentSize()); 1088 } 1089 } 1090 1091 int DocumentHandler::lineHeight(const int &line) 1092 { 1093 QTextDocument *doc = textDocument(); 1094 1095 if (!doc) { 1096 return 0; 1097 } 1098 1099 return int(doc->documentLayout()->blockBoundingRect(doc->findBlockByLineNumber(line)).height()); 1100 } 1101 1102 int DocumentHandler::lineCount() 1103 { 1104 if (!this->textDocument()) 1105 return 0; 1106 return this->textDocument()->blockCount(); 1107 } 1108 1109 int DocumentHandler::getCurrentLineIndex() 1110 { 1111 if (!this->textDocument()) 1112 return -1; 1113 1114 return this->textDocument()->findBlock(m_cursorPosition).blockNumber(); 1115 } 1116 1117 int DocumentHandler::goToLine(const int& line) 1118 { 1119 if (!this->textDocument()) 1120 return this->cursorPosition(); 1121 const auto block = this->textDocument()->findBlockByLineNumber(line); 1122 return block.position() + block.length()-1; 1123 } 1124 1125 void DocumentHandler::setEnableSyntaxHighlighting(const bool &value) 1126 { 1127 if (m_enableSyntaxHighlighting == value) { 1128 return; 1129 } 1130 1131 m_enableSyntaxHighlighting = value; 1132 1133 if (!m_enableSyntaxHighlighting) { 1134 this->setFormatName("None"); 1135 } else { 1136 this->setFormatName(DocumentHandler::getLanguageNameFromFileName(m_fileUrl)); 1137 } 1138 1139 Q_EMIT enableSyntaxHighlightingChanged(); 1140 } 1141 1142 bool DocumentHandler::enableSyntaxHighlighting() const 1143 { 1144 return m_enableSyntaxHighlighting; 1145 } 1146 1147 void DocumentHandler::setTheme(const QString &theme) 1148 { 1149 if (m_theme == theme) 1150 return; 1151 1152 m_theme = theme; 1153 setStyle(); 1154 qDebug() << "changinf the theme<< " << theme << m_theme; 1155 Q_EMIT themeChanged(); 1156 } 1157 1158 QString DocumentHandler::theme() const 1159 { 1160 return m_theme; 1161 }