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 }