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