1 /*
2 * Copyright (C) 1999-2002 Bernd Gehrmann <bernd@mail.berlios.de>
3 * Copyright (c) 2003-2008 André Wöbbeking <Woebbeking@kde.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program 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
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20
21 #include "updateview.h"
22
23 #include <set>
24
25 #include <qapplication.h>
26 #include <QHeaderView>
27 #include <qfileinfo.h>
28 #include <qstack.h>
29 #include <kconfiggroup.h>
30 #include <KLocalizedString>
31
32 #include "cervisiasettings.h"
33 #include "updateview_items.h"
34 #include "updateview_visitors.h"
35
36
37 using Cervisia::Entry;
38 using Cervisia::EntryStatus;
39
40
UpdateView(KConfig & partConfig,QWidget * parent)41 UpdateView::UpdateView(KConfig& partConfig, QWidget *parent)
42 : QTreeWidget(parent),
43 m_partConfig(partConfig),
44 m_unfoldingTree(false)
45 {
46 setAllColumnsShowFocus(true);
47 setUniformRowHeights(true);
48 setRootIsDecorated(false);
49 header()->setSortIndicatorShown(true);
50 setSortingEnabled(true);
51 setSelectionMode(QAbstractItemView::ExtendedSelection);
52
53 setHeaderLabels(QStringList() << i18n("File Name") << i18n("Status") << i18n("Revision")
54 << i18n("Tag/Date") << i18n("Timestamp"));
55
56 header()->resizeSection(0, 280);
57 header()->resizeSection(1, 90);
58 header()->resizeSection(2, 70);
59 header()->resizeSection(3, 90);
60 header()->resizeSection(4, 120);
61
62 setFilter(NoFilter);
63
64 connect( this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)),
65 this, SLOT(itemExecuted(QTreeWidgetItem*,int)) );
66
67 connect( this, SIGNAL(itemExpanded(QTreeWidgetItem*)),
68 this, SLOT(itemExpandedSlot(QTreeWidgetItem*)) );
69
70 KConfigGroup cg(&m_partConfig, "UpdateView");
71 QByteArray state = cg.readEntry<QByteArray>("Columns", QByteArray());
72 header()->restoreState(state);
73 }
74
75
~UpdateView()76 UpdateView::~UpdateView()
77 {
78 KConfigGroup cg(&m_partConfig, "UpdateView");
79 cg.writeEntry("Columns", header()->saveState());
80 }
81
82
setFilter(Filter filter)83 void UpdateView::setFilter(Filter filter)
84 {
85 filt = filter;
86
87 if (UpdateDirItem* item = static_cast<UpdateDirItem*>(topLevelItem(0)))
88 {
89 ApplyFilterVisitor applyFilterVisitor(filter);
90 item->accept(applyFilterVisitor);
91 }
92 }
93
94
filter() const95 UpdateView::Filter UpdateView::filter() const
96 {
97 return filt;
98 }
99
100
101 // returns true iff exactly one UpdateFileItem is selected
hasSingleSelection() const102 bool UpdateView::hasSingleSelection() const
103 {
104 const QList<QTreeWidgetItem *>& listSelectedItems(selectedItems());
105
106 return (listSelectedItems.count() == 1) && isFileItem(listSelectedItems.first());
107 }
108
109
getSingleSelection(QString * filename,QString * revision) const110 void UpdateView::getSingleSelection(QString *filename, QString *revision) const
111 {
112 const QList<QTreeWidgetItem *>& listSelectedItems(selectedItems());
113
114 QString tmpFileName;
115 QString tmpRevision;
116 if ((listSelectedItems.count() == 1) && isFileItem(listSelectedItems.first()))
117 {
118 UpdateFileItem* fileItem(static_cast<UpdateFileItem*>(listSelectedItems.first()));
119 tmpFileName = fileItem->filePath();
120 tmpRevision = fileItem->entry().m_revision;
121 }
122
123 *filename = tmpFileName;
124 if (revision)
125 *revision = tmpRevision;
126 }
127
128
multipleSelection() const129 QStringList UpdateView::multipleSelection() const
130 {
131 QStringList res;
132
133 const QList<QTreeWidgetItem *>& listSelectedItems(selectedItems());
134 foreach (QTreeWidgetItem *item, listSelectedItems)
135 {
136 if (!item->isHidden())
137 res.append(static_cast<UpdateItem*>(item)->filePath());
138 }
139
140 return res;
141 }
142
143
fileSelection() const144 QStringList UpdateView::fileSelection() const
145 {
146 QStringList res;
147
148 const QList<QTreeWidgetItem *>& listSelectedItems(selectedItems());
149 foreach (QTreeWidgetItem *item, listSelectedItems)
150 {
151 if (isFileItem(item) && !item->isHidden())
152 res.append(static_cast<UpdateFileItem*>(item)->filePath());
153 }
154
155 return res;
156 }
157
158
conflictColor() const159 const QColor& UpdateView::conflictColor() const
160 {
161 return m_conflictColor;
162 }
163
164
localChangeColor() const165 const QColor& UpdateView::localChangeColor() const
166 {
167 return m_localChangeColor;
168 }
169
170
remoteChangeColor() const171 const QColor& UpdateView::remoteChangeColor() const
172 {
173 return m_remoteChangeColor;
174 }
175
176
notInCvsColor() const177 const QColor& UpdateView::notInCvsColor() const
178 {
179 return m_notInCvsColor;
180 }
181
182
isUnfoldingTree() const183 bool UpdateView::isUnfoldingTree() const
184 {
185 return m_unfoldingTree;
186 }
187
188
189 // updates internal data
replaceItem(QTreeWidgetItem * oldItem,QTreeWidgetItem * newItem)190 void UpdateView::replaceItem(QTreeWidgetItem *oldItem,
191 QTreeWidgetItem *newItem)
192 {
193 const int index(relevantSelection.indexOf(oldItem));
194 if (index >= 0)
195 relevantSelection.replace(index, newItem);
196 }
197
198
unfoldSelectedFolders()199 void UpdateView::unfoldSelectedFolders()
200 {
201 QApplication::setOverrideCursor(Qt::WaitCursor);
202
203 int previousDepth = 0;
204 bool isUnfolded = false;
205
206 QStringList selection = multipleSelection();
207
208 // setup name of selected folder
209 QString selectedItem = selection.first();
210 if( selectedItem.contains('/') )
211 selectedItem.remove(0, selectedItem.lastIndexOf('/')+1);
212
213 // avoid flicker
214 const bool _updatesEnabled = updatesEnabled();
215 setUpdatesEnabled(false);
216
217 QTreeWidgetItemIterator it(this);
218 while ( QTreeWidgetItem *item = (*it) )
219 {
220 if ( isDirItem(item) )
221 {
222 UpdateDirItem* dirItem = static_cast<UpdateDirItem*>(item);
223
224 // below selected folder?
225 if( previousDepth && dirItem->depth() > previousDepth )
226 {
227 // if this dir wasn't scanned already scan it recursive
228 // (this is only a hack to reduce the processEvents() calls,
229 // setOpen() would scan the dir too)
230 if (dirItem->wasScanned() == false)
231 {
232 const bool recursive = true;
233 dirItem->maybeScanDir(recursive);
234
235 // scanning can take some time so keep the gui alive
236 qApp->processEvents();
237 }
238
239 dirItem->setOpen(!isUnfolded);
240 }
241 // selected folder?
242 else if( selectedItem == dirItem->entry().m_name )
243 {
244 previousDepth = dirItem->depth();
245 isUnfolded = dirItem->isExpanded();
246
247 // if this dir wasn't scanned already scan it recursive
248 // (this is only a hack to reduce the processEvents() calls,
249 // setOpen() would scan the dir too)
250 if (dirItem->wasScanned() == false)
251 {
252 const bool recursive = true;
253 dirItem->maybeScanDir(recursive);
254
255 // scanning can take some time so keep the gui alive
256 qApp->processEvents();
257 }
258
259 dirItem->setOpen(!isUnfolded);
260 }
261 // back to the level of the selected folder or above?
262 else if( previousDepth && dirItem->depth() >= previousDepth )
263 {
264 previousDepth = 0;
265 }
266 }
267
268 ++it;
269 }
270
271 // maybe some UpdateDirItem was opened the first time so check the whole tree
272 setFilter(filter());
273
274 setUpdatesEnabled(_updatesEnabled);
275 viewport()->update();
276
277 QApplication::restoreOverrideCursor();
278 }
279
280
unfoldTree()281 void UpdateView::unfoldTree()
282 {
283 QApplication::setOverrideCursor(Qt::WaitCursor);
284
285 m_unfoldingTree = true;
286
287 const bool _updatesEnabled = updatesEnabled();
288
289 setUpdatesEnabled(false);
290
291 QTreeWidgetItemIterator it(this);
292 while (QTreeWidgetItem *item = (*it) )
293 {
294 if (isDirItem(item))
295 {
296 UpdateDirItem* dirItem(static_cast<UpdateDirItem*>(item));
297
298 // if this dir wasn't scanned already scan it recursive
299 // (this is only a hack to reduce the processEvents() calls,
300 // setOpen() would scan the dir too)
301 if (dirItem->wasScanned() == false)
302 {
303 const bool recursive(true);
304 dirItem->maybeScanDir(recursive);
305
306 // scanning can take some time so keep the gui alive
307 qApp->processEvents();
308 }
309
310 dirItem->setOpen(true);
311 }
312
313 ++it;
314 }
315
316 // maybe some UpdateDirItem was opened the first time so check the whole tree
317 setFilter(filter());
318
319 setUpdatesEnabled(_updatesEnabled);
320
321 viewport()->update();
322
323 m_unfoldingTree = false;
324
325 QApplication::restoreOverrideCursor();
326 }
327
328
foldTree()329 void UpdateView::foldTree()
330 {
331 QTreeWidgetItemIterator it(this);
332 while (QTreeWidgetItem* item = (*it) )
333 {
334 // don't close the top level directory
335 if (isDirItem(item) && item->parent())
336 item->setExpanded(false);
337
338 ++it;
339 }
340 }
341
342
343 /**
344 * Clear the tree view and insert the directory dirname
345 * into it as the new root item
346 */
openDirectory(const QString & dirName)347 void UpdateView::openDirectory(const QString& dirName)
348 {
349 clear();
350
351 // do this each time as the configuration could be changed
352 updateColors();
353
354 Entry entry;
355 entry.m_name = dirName;
356 entry.m_type = Entry::Dir;
357
358 UpdateDirItem *item = new UpdateDirItem(this, entry);
359 item->setExpanded(true);
360 setCurrentItem(item);
361 item->setSelected(true);
362 }
363
364
365 /**
366 * Start a job. We want to be able to change the status field
367 * correctly afterwards, so we have to remember the current
368 * selection (which the user may change during the update).
369 * In the recursive case, we collect all relevant directories.
370 * Furthermore, we have to change the items to undefined state.
371 */
prepareJob(bool recursive,Action action)372 void UpdateView::prepareJob(bool recursive, Action action)
373 {
374 act = action;
375
376 // Scan recursively all entries - there's no way around this here
377 if (recursive)
378 static_cast<UpdateDirItem*>(topLevelItem(0))->maybeScanDir(true);
379
380 rememberSelection(recursive);
381 if (act != Add)
382 markUpdated(false, false);
383 }
384
385
386 /**
387 * Finishes a job. What we do depends a bit on
388 * whether the command was successful or not.
389 */
finishJob(bool normalExit,int exitStatus)390 void UpdateView::finishJob(bool normalExit, int exitStatus)
391 {
392 // cvs exitStatus == 1 only means that there're conflicts
393 // ... which is not correct (e.g. server not reachable also returns 1)
394 const bool success(normalExit && (exitStatus == 0));
395
396 if (act != Add)
397 markUpdated(true, success);
398 syncSelection();
399
400 // maybe some new items were created or
401 // visibility of items changed so check the whole tree
402 setFilter(filter());
403 }
404
405
406 /**
407 * Marking non-selected items in a directory updated (as a consequence
408 * of not appearing in 'cvs update' output) is done in two steps: In the
409 * first, they are marked as 'indefinite', so that their status on the screen
410 * isn't misrepresented. In the second step, they are either set
411 * to 'UpToDate' (success=true) or 'Unknown'.
412 */
markUpdated(bool laststage,bool success)413 void UpdateView::markUpdated(bool laststage, bool success)
414 {
415 foreach (QTreeWidgetItem* it, relevantSelection)
416 {
417 if (isDirItem(it))
418 {
419 for (int i = 0; i < it->childCount(); i++)
420 {
421 QTreeWidgetItem *item = it->child(i);
422 if (isFileItem(item))
423 {
424 UpdateFileItem* fileItem = static_cast<UpdateFileItem*>(item);
425 fileItem->markUpdated(laststage, success);
426 }
427 }
428 }
429 else
430 {
431 UpdateFileItem* fileItem = static_cast<UpdateFileItem*>(it);
432 fileItem->markUpdated(laststage, success);
433 }
434 }
435 }
436
437
438 /**
439 * Remember the selection, see prepareJob()
440 */
rememberSelection(bool recursive)441 void UpdateView::rememberSelection(bool recursive)
442 {
443 std::set<QTreeWidgetItem*> setItems;
444 for (QTreeWidgetItemIterator it(this); *it; ++it)
445 {
446 QTreeWidgetItem* item(*it);
447
448 // if this item is selected and if it was not inserted already
449 // and if we work recursive and if it is a dir item then insert
450 // all sub dirs
451 // DON'T CHANGE TESTING ORDER
452 if (item->isSelected()
453 && setItems.insert(item).second
454 && recursive
455 && isDirItem(item))
456 {
457 QStack<QTreeWidgetItem *> s;
458 int childNum = 0;
459 QTreeWidgetItem *startItem = item;
460 QTreeWidgetItem *childItem = startItem->child(childNum);
461 while ( childItem )
462 {
463 // if this item is a dir item and if it was not
464 // inserted already then insert all sub dirs
465 // DON'T CHANGE TESTING ORDER
466 if (isDirItem(childItem) && setItems.insert(childItem).second)
467 {
468 if (QTreeWidgetItem *childChildItem = childItem->child(0))
469 s.push(childChildItem);
470 }
471
472 if ( ++childNum < startItem->childCount() )
473 childItem = startItem->child(childNum);
474 else
475 {
476 if ( s.isEmpty() )
477 break;
478 else
479 {
480 childItem = s.pop();
481 startItem = childItem->parent();
482 childNum = 0;
483 }
484 }
485 }
486 }
487 }
488
489 // Copy the set to the list
490 relevantSelection.clear();
491 std::set<QTreeWidgetItem *>::const_iterator const itItemEnd = setItems.end();
492 for (std::set<QTreeWidgetItem *>::const_iterator itItem = setItems.begin();
493 itItem != itItemEnd; ++itItem)
494 relevantSelection.append(*itItem);
495
496 #if 0
497 qDebug() << "Relevant:";
498 foreach (QTreeWidgetItem * item, relevantSelection)
499 qDebug() << " " << item->text(0);
500 qDebug() << "End";
501 #endif
502 }
503
504
505 /**
506 * Use the remembered selection to resynchronize
507 * with the actual directory and Entries content.
508 */
syncSelection()509 void UpdateView::syncSelection()
510 {
511 // compute all directories which are selected or contain a selected file
512 // (in recursive mode this includes all sub directories)
513 std::set<UpdateDirItem*> setDirItems;
514 foreach (QTreeWidgetItem* item, relevantSelection)
515 {
516 UpdateDirItem *dirItem(0);
517 if (isDirItem(item))
518 dirItem = static_cast<UpdateDirItem*>(item);
519 else if (QTreeWidgetItem *parentItem = item->parent())
520 dirItem = static_cast<UpdateDirItem*>(parentItem);
521
522 if (dirItem)
523 setDirItems.insert(dirItem);
524 }
525
526 QApplication::setOverrideCursor(Qt::WaitCursor);
527
528 std::set<UpdateDirItem*>::const_iterator const itDirItemEnd = setDirItems.end();
529 for (std::set<UpdateDirItem*>::const_iterator itDirItem = setDirItems.begin();
530 itDirItem != itDirItemEnd; ++itDirItem)
531 {
532 UpdateDirItem* dirItem = *itDirItem;
533
534 dirItem->syncWithDirectory();
535 dirItem->syncWithEntries();
536
537 qApp->processEvents();
538 }
539
540 QApplication::restoreOverrideCursor();
541 }
542
543
544 /**
545 * Get the colors from the configuration each time the list view items
546 * are created.
547 */
updateColors()548 void UpdateView::updateColors()
549 {
550 KConfigGroup cs(&m_partConfig, "Colors");
551
552 m_conflictColor = cs.readEntry("Conflict", QColor(255, 130, 130) );
553 m_localChangeColor = cs.readEntry("LocalChange", QColor(130, 130, 255));
554 m_remoteChangeColor = cs.readEntry("RemoteChange", QColor(70, 210, 70) );
555
556 m_notInCvsColor = CervisiaSettings::notInCvsColor();
557 }
558
559
560 /**
561 * Process one line from the output of 'cvs update'. If parseAsStatus
562 * is true, it is assumed that the output is from a command
563 * 'cvs update -n', i.e. cvs actually changes no files.
564 */
processUpdateLine(QString str)565 void UpdateView::processUpdateLine(QString str)
566 {
567 if (str.length() > 2 && str[1] == ' ')
568 {
569 EntryStatus status(Cervisia::Unknown);
570 switch (str[0].toLatin1())
571 {
572 case 'C':
573 status = Cervisia::Conflict;
574 break;
575 case 'A':
576 status = Cervisia::LocallyAdded;
577 break;
578 case 'R':
579 status = Cervisia::LocallyRemoved;
580 break;
581 case 'M':
582 status = Cervisia::LocallyModified;
583 break;
584 case 'U':
585 status = (act == UpdateNoAct) ? Cervisia::NeedsUpdate : Cervisia::Updated;
586 break;
587 case 'P':
588 status = (act == UpdateNoAct) ? Cervisia::NeedsPatch : Cervisia::Patched;
589 break;
590 case '?':
591 status = Cervisia::NotInCVS;
592 break;
593 default:
594 return;
595 }
596 updateItem(str.mid(2), status, false);
597 }
598
599 const QString removedFileStart(QLatin1String("cvs server: "));
600 const QString removedFileEnd(QLatin1String(" is no longer in the repository"));
601 if (str.startsWith(removedFileStart) && str.endsWith(removedFileEnd))
602 {
603 }
604
605 #if 0
606 else if (str.left(21) == "cvs server: Updating " ||
607 str.left(21) == "cvs update: Updating ")
608 updateItem(str.right(str.length()-21), Unknown, true);
609 #endif
610 }
611
612
updateItem(const QString & filePath,EntryStatus status,bool isdir)613 void UpdateView::updateItem(const QString& filePath, EntryStatus status, bool isdir)
614 {
615 if (isdir && filePath == QLatin1String("."))
616 return;
617
618 const QFileInfo fileInfo(filePath);
619
620 UpdateDirItem* rootItem = static_cast<UpdateDirItem*>(topLevelItem(0));
621 UpdateDirItem* dirItem = findOrCreateDirItem(fileInfo.path(), rootItem);
622
623 dirItem->updateChildItem(fileInfo.fileName(), status, isdir);
624 }
625
626
itemExecuted(QTreeWidgetItem * item,int)627 void UpdateView::itemExecuted(QTreeWidgetItem *item, int)
628 {
629 if (isFileItem(item))
630 emit fileOpened(static_cast<UpdateFileItem*>(item)->filePath());
631 }
632
itemExpandedSlot(QTreeWidgetItem * item)633 void UpdateView::itemExpandedSlot(QTreeWidgetItem *item)
634 {
635 static_cast<UpdateItem *>(item)->setOpen(true);
636 }
637
638
639 // Local Variables:
640 // c-basic-offset: 4
641 // End:
642