1 | /* This file is part of the Kate project. |
2 | * |
3 | * Copyright (C) 2012 Christoph Cullmann <cullmann@kde.org> |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Library General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Library General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Library General Public License |
16 | * along with this library; see the file COPYING.LIB. If not, write to |
17 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
18 | * Boston, MA 02110-1301, USA. |
19 | */ |
20 | |
21 | #include "kateproject.h" |
22 | #include "kateprojectworker.h" |
23 | |
24 | #include <klocale.h> |
25 | |
26 | #include <ktexteditor/document.h> |
27 | |
28 | #include <QDir> |
29 | #include <QFile> |
30 | #include <QFileInfo> |
31 | #include <QPlainTextDocumentLayout> |
32 | #include <qjson/parser.h> |
33 | |
34 | KateProject::KateProject () |
35 | : QObject () |
36 | , m_worker (new KateProjectWorker (this)) |
37 | , m_thread (m_worker) |
38 | , m_notesDocument (0) |
39 | , m_documentsParent (0) |
40 | { |
41 | /** |
42 | * move worker object over and start our worker thread |
43 | * thread will delete worker on run() exit |
44 | */ |
45 | m_worker->moveToThread (&m_thread); |
46 | m_thread.start (); |
47 | } |
48 | |
49 | KateProject::~KateProject () |
50 | { |
51 | /** |
52 | * only do this once |
53 | */ |
54 | Q_ASSERT (m_worker); |
55 | |
56 | /** |
57 | * quit the thread event loop and wait for completion |
58 | * will delete worker on thread run() exit |
59 | */ |
60 | m_thread.quit (); |
61 | m_thread.wait (); |
62 | |
63 | /** |
64 | * marks as deleted |
65 | */ |
66 | m_worker = 0; |
67 | |
68 | /** |
69 | * save notes document, if any |
70 | */ |
71 | saveNotesDocument (); |
72 | } |
73 | |
74 | bool KateProject::load (const QString &fileName) |
75 | { |
76 | /** |
77 | * bail out if already fileName set! |
78 | */ |
79 | if (!m_fileName.isEmpty()) |
80 | return false; |
81 | |
82 | /** |
83 | * set new filename and base directory |
84 | */ |
85 | m_fileName = fileName; |
86 | m_baseDir = QFileInfo(m_fileName).canonicalPath(); |
87 | |
88 | /** |
89 | * trigger reload |
90 | */ |
91 | return reload (); |
92 | } |
93 | |
94 | bool KateProject::reload (bool force) |
95 | { |
96 | /** |
97 | * open the file for reading, bail out on error! |
98 | */ |
99 | QFile file (m_fileName); |
100 | if (!file.open (QFile::ReadOnly)) |
101 | return false; |
102 | |
103 | /** |
104 | * parse the whole file, bail out again on error! |
105 | */ |
106 | bool ok = true; |
107 | QJson::Parser parser; |
108 | QVariant project = parser.parse (&file, &ok); |
109 | if (!ok) |
110 | return false; |
111 | |
112 | /** |
113 | * now: get global group |
114 | */ |
115 | QVariantMap globalProject = project.toMap (); |
116 | |
117 | /** |
118 | * no name, bad => bail out |
119 | */ |
120 | if (globalProject["name" ].toString().isEmpty()) |
121 | return false; |
122 | |
123 | /** |
124 | * support out-of-source project files |
125 | */ |
126 | if (!globalProject["directory" ].toString().isEmpty()) |
127 | m_baseDir = QFileInfo (globalProject["directory" ].toString()).canonicalFilePath (); |
128 | |
129 | /** |
130 | * anything changed? |
131 | * else be done without forced reload! |
132 | */ |
133 | if (!force && (m_projectMap == globalProject)) |
134 | return true; |
135 | |
136 | /** |
137 | * setup global attributes in this object |
138 | */ |
139 | m_projectMap = globalProject; |
140 | |
141 | /** |
142 | * emit that we changed stuff |
143 | */ |
144 | emit projectMapChanged (); |
145 | |
146 | /** |
147 | * trigger worker to REALLY load the project model and stuff |
148 | */ |
149 | QMetaObject::invokeMethod (m_worker, "loadProject" , Qt::QueuedConnection, Q_ARG(QString, m_baseDir), Q_ARG(QVariantMap, m_projectMap)); |
150 | |
151 | /** |
152 | * done ok ;) |
153 | */ |
154 | return true; |
155 | } |
156 | |
157 | void KateProject::loadProjectDone (KateProjectSharedQStandardItem topLevel, KateProjectSharedQMapStringItem file2Item) |
158 | { |
159 | /** |
160 | * setup model data |
161 | */ |
162 | m_model.clear (); |
163 | m_model.invisibleRootItem()->appendColumn (topLevel->takeColumn (0)); |
164 | |
165 | /** |
166 | * setup file => item map |
167 | */ |
168 | m_file2Item = file2Item; |
169 | |
170 | /** |
171 | * readd the documents that are open atm |
172 | */ |
173 | m_documentsParent = 0; |
174 | foreach (KTextEditor::Document *document, m_documents.keys ()) |
175 | registerDocument (document); |
176 | |
177 | /** |
178 | * model changed |
179 | */ |
180 | emit modelChanged (); |
181 | } |
182 | |
183 | void KateProject::loadIndexDone (KateProjectSharedProjectIndex projectIndex) |
184 | { |
185 | /** |
186 | * move to our project |
187 | */ |
188 | m_projectIndex = projectIndex; |
189 | |
190 | /** |
191 | * notify external world that data is available |
192 | */ |
193 | emit indexChanged (); |
194 | } |
195 | |
196 | QFile *KateProject::projectLocalFile (const QString &file) const |
197 | { |
198 | /** |
199 | * nothing on empty file names for project |
200 | * should not happen |
201 | */ |
202 | if (m_fileName.isEmpty()) |
203 | return 0; |
204 | |
205 | /** |
206 | * create dir to store local files, else fail |
207 | */ |
208 | if (!QDir().mkpath (m_fileName+".d" )) |
209 | return 0; |
210 | |
211 | /** |
212 | * try to open file read-write |
213 | */ |
214 | QFile *readWriteFile = new QFile (m_fileName + ".d" + QDir::separator() + file); |
215 | if (!readWriteFile->open (QIODevice::ReadWrite)) { |
216 | delete readWriteFile; |
217 | return 0; |
218 | } |
219 | |
220 | /** |
221 | * all fine, return file |
222 | */ |
223 | return readWriteFile; |
224 | } |
225 | |
226 | QTextDocument* KateProject::notesDocument () |
227 | { |
228 | /** |
229 | * already there? |
230 | */ |
231 | if (m_notesDocument) |
232 | return m_notesDocument; |
233 | |
234 | /** |
235 | * else create it |
236 | */ |
237 | m_notesDocument = new QTextDocument (this); |
238 | m_notesDocument->setDocumentLayout (new QPlainTextDocumentLayout (m_notesDocument)); |
239 | |
240 | /** |
241 | * and load text if possible |
242 | */ |
243 | if (QFile *inFile = projectLocalFile ("notes.txt" )) { |
244 | { |
245 | QTextStream inStream (inFile); |
246 | inStream.setCodec ("UTF-8" ); |
247 | m_notesDocument->setPlainText (inStream.readAll ()); |
248 | } |
249 | delete inFile; |
250 | } |
251 | |
252 | /** |
253 | * and be done |
254 | */ |
255 | return m_notesDocument; |
256 | } |
257 | |
258 | void KateProject::saveNotesDocument () |
259 | { |
260 | /** |
261 | * no notes document, nothing to do |
262 | */ |
263 | if (!m_notesDocument) |
264 | return; |
265 | |
266 | /** |
267 | * try to get file to save to |
268 | */ |
269 | if (QFile *outFile = projectLocalFile ("notes.txt" )) { |
270 | outFile->resize (0); |
271 | { |
272 | QTextStream outStream (outFile); |
273 | outStream.setCodec ("UTF-8" ); |
274 | outStream << m_notesDocument->toPlainText (); |
275 | } |
276 | delete outFile; |
277 | } |
278 | } |
279 | |
280 | |
281 | void KateProject::slotModifiedChanged(KTextEditor::Document* document) { |
282 | KateProjectItem *item = itemForFile (m_documents.value (document)); |
283 | |
284 | if (!item) return; |
285 | |
286 | item->slotModifiedChanged(document); |
287 | } |
288 | |
289 | void KateProject::slotModifiedOnDisk (KTextEditor::Document *document, |
290 | bool isModified, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { |
291 | |
292 | KateProjectItem *item = itemForFile (m_documents.value (document)); |
293 | |
294 | if (!item) return; |
295 | |
296 | item->slotModifiedOnDisk(document,isModified, reason); |
297 | |
298 | } |
299 | |
300 | |
301 | void KateProject::registerDocument (KTextEditor::Document *document) |
302 | { |
303 | // remember the document, if not already there |
304 | if (!m_documents.contains(document)) |
305 | m_documents[document] = document->url().toLocalFile (); |
306 | |
307 | // try to get item for the document |
308 | KateProjectItem *item = itemForFile (document->url().toLocalFile ()); |
309 | |
310 | // if we got one, we are done, else create a dummy! |
311 | if (item) { |
312 | disconnect(document,SIGNAL(modifiedChanged(KTextEditor::Document *)),this,SLOT(slotModifiedChanged(KTextEditor::Document *))); |
313 | disconnect(document,SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)),this,SLOT(slotModifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason))); |
314 | item->slotModifiedChanged(document); |
315 | |
316 | /*FIXME item->slotModifiedOnDisk(document,document->isModified(),qobject_cast<KTextEditor::ModificationInterface*>(document)->modifiedOnDisk()); FIXME*/ |
317 | |
318 | connect(document,SIGNAL(modifiedChanged(KTextEditor::Document *)),this,SLOT(slotModifiedChanged(KTextEditor::Document *))); |
319 | connect(document,SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)),this,SLOT(slotModifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason))); |
320 | |
321 | return; |
322 | } |
323 | |
324 | // perhaps create the parent item |
325 | if (!m_documentsParent) { |
326 | m_documentsParent = new KateProjectItem (KateProjectItem::Directory, i18n ("<untracked>" )); |
327 | m_model.insertRow (0, m_documentsParent); |
328 | } |
329 | |
330 | // create document item |
331 | QFileInfo fileInfo (document->url().toLocalFile ()); |
332 | KateProjectItem *fileItem = new KateProjectItem (KateProjectItem::File, fileInfo.fileName()); |
333 | fileItem->setData(document->url().toLocalFile (), Qt::ToolTipRole); |
334 | fileItem->slotModifiedChanged(document); |
335 | connect(document,SIGNAL(modifiedChanged(KTextEditor::Document *)),this,SLOT(slotModifiedChanged(KTextEditor::Document *))); |
336 | connect(document,SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)),this,SLOT(slotModifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason))); |
337 | |
338 | |
339 | bool inserted = false; |
340 | for (int i = 0; i < m_documentsParent->rowCount(); ++i) { |
341 | if (m_documentsParent->child (i)->data(Qt::UserRole).toString() > document->url().toLocalFile ()) { |
342 | m_documentsParent->insertRow (i, fileItem); |
343 | inserted = true; |
344 | break; |
345 | } |
346 | } |
347 | if (!inserted) |
348 | m_documentsParent->appendRow (fileItem); |
349 | |
350 | fileItem->setData (document->url().toLocalFile (), Qt::UserRole); |
351 | fileItem->setData (QVariant (true), Qt::UserRole + 3); |
352 | |
353 | if (!m_file2Item) |
354 | m_file2Item = KateProjectSharedQMapStringItem (new QMap<QString, KateProjectItem *> ()); |
355 | (*m_file2Item)[document->url().toLocalFile ()] = fileItem; |
356 | } |
357 | |
358 | void KateProject::unregisterDocument (KTextEditor::Document *document) |
359 | { |
360 | // skip if no works |
361 | if (!m_documents.contains (document)) |
362 | return; |
363 | |
364 | // perhaps kill the item we have generated |
365 | bool empty = false; |
366 | if (KateProjectItem *item = (KateProjectItem*)itemForFile (m_documents.value (document))) { |
367 | disconnect(document,SIGNAL(modifiedChanged(KTextEditor::Document *)),this,SLOT(slotModifiedChanged(KTextEditor::Document *))); |
368 | if (m_documentsParent && item->data (Qt::UserRole + 3).toBool ()) { |
369 | for (int i = 0; i < m_documentsParent->rowCount(); ++i) { |
370 | if (m_documentsParent->child (i) == item) { |
371 | m_documentsParent->removeRow (i); |
372 | break; |
373 | } |
374 | } |
375 | |
376 | empty = !m_documentsParent->rowCount(); |
377 | |
378 | m_file2Item->remove (m_documents.value (document)); |
379 | } |
380 | } |
381 | |
382 | // forget the document |
383 | m_documents.remove (document); |
384 | |
385 | // perhaps remove parent item |
386 | if (m_documentsParent && empty) { |
387 | m_model.removeRow (0); |
388 | m_documentsParent = 0; |
389 | } |
390 | } |
391 | |
392 | // kate: space-indent on; indent-width 2; replace-tabs on; |
393 | |