1 /****************************************************************************
2 **
3 ** Copyright (C) 2021 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "annotationcommenttab.h"
27 #include "ui_annotationcommenttab.h"
28 
29 #include "defaultannotations.h"
30 
31 #include <qmldesignerplugin.h>
32 #include <richtexteditor/richtexteditor.h>
33 #include <projectexplorer/target.h>
34 #include <qmlprojectmanager/qmlproject.h>
35 
36 #include <QCryptographicHash>
37 
38 namespace QmlDesigner {
39 
AnnotationCommentTab(QWidget * parent)40 AnnotationCommentTab::AnnotationCommentTab(QWidget *parent)
41     : QWidget(parent)
42     , ui(std::make_unique<Ui::AnnotationCommentTab>())
43 {
44     ui->setupUi(this);
45 
46     m_editor = new RichTextEditor{this};
47 
48     connect(m_editor, &RichTextEditor::insertingImage, this, [this](QString &filePath) {
49         filePath = backupFile(filePath);
50     });
51 
52     m_editor->setImageActionVisible(false);
53 
54     const QmlDesigner::DesignDocument *designDocument = QmlDesigner::QmlDesignerPlugin::instance()
55             ->documentManager().currentDesignDocument();
56     Utils::FilePath projectPath;
57 
58     Q_ASSERT(designDocument);
59 
60     if (designDocument) {
61         if (designDocument->currentTarget() && designDocument->currentTarget()->project()) {
62             projectPath = designDocument->currentTarget()->project()->projectFilePath();
63             m_editor->setImageActionVisible(true);
64         }
65 
66         if (projectPath.isEmpty()) {
67             projectPath = designDocument->fileName();
68             m_editor->setImageActionVisible(false);
69         }
70 
71         m_editor->setDocumentBaseUrl(QUrl::fromLocalFile(projectPath.toString()));
72     }
73 
74     ui->formLayout->setWidget(3, QFormLayout::FieldRole, m_editor);
75 
76     connect(ui->titleEdit, &QComboBox::currentTextChanged, this, [this](QString const &text) {
77         emit titleChanged(text, this);
78     });
79 }
80 
~AnnotationCommentTab()81 AnnotationCommentTab::~AnnotationCommentTab() {}
82 
currentComment() const83 Comment AnnotationCommentTab::currentComment() const
84 {
85     Comment result;
86 
87     result.setTitle(ui->titleEdit->currentText().trimmed());
88     result.setAuthor(ui->authorEdit->text().trimmed());
89     if (defaultAnnotations() && !defaultAnnotations()->isRichText(result)) {
90         result.setText(m_editor->plainText().trimmed());
91     } else
92         result.setText(m_editor->richText().trimmed());
93 
94     if (m_comment.sameContent(result))
95         result.setTimestamp(m_comment.timestamp());
96     else
97         result.updateTimestamp();
98 
99     return result;
100 }
101 
originalComment() const102 Comment AnnotationCommentTab::originalComment() const
103 {
104     return m_comment;
105 }
106 
setComment(const Comment & comment)107 void AnnotationCommentTab::setComment(const Comment &comment)
108 {
109     m_comment = comment;
110     resetUI();
111 }
112 
resetUI()113 void AnnotationCommentTab::resetUI()
114 {
115     ui->titleEdit->setCurrentText(m_comment.title());
116     ui->authorEdit->setText(m_comment.author());
117     m_editor->setRichText(m_comment.deescapedText());
118 
119     if (m_comment.timestamp() > 0)
120         ui->timeLabel->setText(m_comment.timestampStr());
121     else
122         ui->timeLabel->setText("");
123 }
124 
resetComment()125 void AnnotationCommentTab::resetComment()
126 {
127     m_comment = currentComment();
128 }
129 
defaultAnnotations() const130 DefaultAnnotationsModel *AnnotationCommentTab::defaultAnnotations() const
131 {
132     return m_defaults;
133 }
134 
setDefaultAnnotations(DefaultAnnotationsModel * defaults)135 void AnnotationCommentTab::setDefaultAnnotations(DefaultAnnotationsModel *defaults)
136 {
137     m_defaults = defaults;
138     ui->titleEdit->setModel(m_defaults);
139 }
140 
backupFile(const QString & filePath)141 QString AnnotationCommentTab::backupFile(const QString &filePath)
142 {
143     const QmlDesigner::DesignDocument *designDocument = QmlDesigner::QmlDesignerPlugin::instance()
144             ->documentManager().currentDesignDocument();
145     Utils::FilePath projectFolderPath;
146 
147     Q_ASSERT(designDocument);
148 
149     if (designDocument) {
150         if (designDocument->hasProject())
151             projectFolderPath = designDocument->projectFolder();
152         if (projectFolderPath.isEmpty())
153             projectFolderPath = designDocument->fileName().parentDir();
154     }
155     else
156         return {};
157 
158     const QDir projDir(projectFolderPath.toDir());
159 
160     if (!projDir.exists())
161         return {};
162 
163     const QString imageSubDir(".AnnotationImages");
164     const QDir imgDir(projDir.absolutePath() + QDir::separator() + imageSubDir);
165 
166     ensureDir(imgDir);
167 
168     Q_ASSERT(imgDir.exists());
169     if (!imgDir.exists())
170         return {};
171 
172     const QFileInfo oldFile(filePath);
173     QFileInfo newFile(imgDir, oldFile.fileName());
174 
175     QString newName = newFile.baseName() + "_%1." + newFile.completeSuffix();
176 
177     for (size_t i = 1; true; ++i) {
178         if (!newFile.exists()) {
179             QFile(oldFile.absoluteFilePath()).copy(newFile.absoluteFilePath());
180             break;
181         } else if (compareFileChecksum(oldFile.absoluteFilePath(), newFile.absoluteFilePath()) == 0)
182             break;
183 
184         newFile.setFile(imgDir, newName.arg(i));
185     }
186 
187     return projDir.relativeFilePath(newFile.absoluteFilePath());
188 }
189 
ensureDir(const QDir & dir)190 void AnnotationCommentTab::ensureDir(const QDir &dir)
191 {
192     if (!dir.exists())
193         dir.mkdir(".");
194 }
195 
compareFileChecksum(const QString & firstFile,const QString & secondFile)196 int AnnotationCommentTab::compareFileChecksum(const QString &firstFile, const QString &secondFile)
197 {
198     QCryptographicHash sum1(QCryptographicHash::Md5);
199 
200     {
201         QFile f1(firstFile);
202         if (f1.open(QFile::ReadOnly)) {
203             sum1.addData(&f1);
204         }
205     }
206 
207     QCryptographicHash sum2(QCryptographicHash::Md5);
208 
209     {
210         QFile f2(secondFile);
211         if (f2.open(QFile::ReadOnly)) {
212             sum2.addData(&f2);
213         }
214     }
215 
216     return sum1.result().compare(sum2.result());
217 }
218 
219 } //namespace QmlDesigner
220