Warning, file /sdk/cervisia/updateview.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  * Copyright (C) 1999-2002 Bernd Gehrmann <bernd@mail.berlios.de>
0003  * Copyright (c) 2003-2008 André Wöbbeking <Woebbeking@kde.org>
0004  *
0005  * This program is free software; you can redistribute it and/or modify
0006  * it under the terms of the GNU General Public License as published by
0007  * the Free Software Foundation; either version 2 of the License, or
0008  * (at your option) any later version.
0009  *
0010  * This program is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  * GNU General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU General Public License
0016  * along with this program; if not, write to the Free Software
0017  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
0018  */
0019 
0020 #include "updateview.h"
0021 
0022 #include <set>
0023 
0024 #include <KLocalizedString>
0025 #include <QHeaderView>
0026 #include <kconfiggroup.h>
0027 #include <qapplication.h>
0028 #include <qfileinfo.h>
0029 #include <qstack.h>
0030 
0031 #include "cervisiasettings.h"
0032 #include "updateview_items.h"
0033 #include "updateview_visitors.h"
0034 
0035 using Cervisia::Entry;
0036 using Cervisia::EntryStatus;
0037 
0038 UpdateView::UpdateView(KConfig &partConfig, QWidget *parent)
0039     : QTreeWidget(parent)
0040     , m_partConfig(partConfig)
0041     , m_unfoldingTree(false)
0042 {
0043     setAllColumnsShowFocus(true);
0044     setUniformRowHeights(true);
0045     setRootIsDecorated(false);
0046     header()->setSortIndicatorShown(true);
0047     setSortingEnabled(true);
0048     setSelectionMode(QAbstractItemView::ExtendedSelection);
0049 
0050     setHeaderLabels(QStringList() << i18n("File Name") << i18n("Status") << i18n("Revision") << i18n("Tag/Date") << i18n("Timestamp"));
0051 
0052     header()->resizeSection(0, 280);
0053     header()->resizeSection(1, 90);
0054     header()->resizeSection(2, 70);
0055     header()->resizeSection(3, 90);
0056     header()->resizeSection(4, 120);
0057 
0058     setFilter(NoFilter);
0059 
0060     connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(itemExecuted(QTreeWidgetItem *, int)));
0061 
0062     connect(this, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(itemExpandedSlot(QTreeWidgetItem *)));
0063 
0064     KConfigGroup cg(&m_partConfig, "UpdateView");
0065     QByteArray state = cg.readEntry<QByteArray>("Columns", QByteArray());
0066     header()->restoreState(state);
0067 }
0068 
0069 UpdateView::~UpdateView()
0070 {
0071     KConfigGroup cg(&m_partConfig, "UpdateView");
0072     cg.writeEntry("Columns", header()->saveState());
0073 }
0074 
0075 void UpdateView::setFilter(Filter filter)
0076 {
0077     filt = filter;
0078 
0079     if (auto item = static_cast<UpdateDirItem *>(topLevelItem(0))) {
0080         ApplyFilterVisitor applyFilterVisitor(filter);
0081         item->accept(applyFilterVisitor);
0082     }
0083 }
0084 
0085 UpdateView::Filter UpdateView::filter() const
0086 {
0087     return filt;
0088 }
0089 
0090 // returns true iff exactly one UpdateFileItem is selected
0091 bool UpdateView::hasSingleSelection() const
0092 {
0093     const QList<QTreeWidgetItem *> &listSelectedItems(selectedItems());
0094 
0095     return (listSelectedItems.count() == 1) && isFileItem(listSelectedItems.first());
0096 }
0097 
0098 void UpdateView::getSingleSelection(QString *filename, QString *revision) const
0099 {
0100     const QList<QTreeWidgetItem *> &listSelectedItems(selectedItems());
0101 
0102     QString tmpFileName;
0103     QString tmpRevision;
0104     if ((listSelectedItems.count() == 1) && isFileItem(listSelectedItems.first())) {
0105         auto fileItem(static_cast<UpdateFileItem *>(listSelectedItems.first()));
0106         tmpFileName = fileItem->filePath();
0107         tmpRevision = fileItem->entry().m_revision;
0108     }
0109 
0110     *filename = tmpFileName;
0111     if (revision)
0112         *revision = tmpRevision;
0113 }
0114 
0115 QStringList UpdateView::multipleSelection() const
0116 {
0117     QStringList res;
0118 
0119     const QList<QTreeWidgetItem *> &listSelectedItems(selectedItems());
0120     foreach (QTreeWidgetItem *item, listSelectedItems) {
0121         if (!item->isHidden())
0122             res.append(static_cast<UpdateItem *>(item)->filePath());
0123     }
0124 
0125     return res;
0126 }
0127 
0128 QStringList UpdateView::fileSelection() const
0129 {
0130     QStringList res;
0131 
0132     const QList<QTreeWidgetItem *> &listSelectedItems(selectedItems());
0133     foreach (QTreeWidgetItem *item, listSelectedItems) {
0134         if (isFileItem(item) && !item->isHidden())
0135             res.append(static_cast<UpdateFileItem *>(item)->filePath());
0136     }
0137 
0138     return res;
0139 }
0140 
0141 const QColor &UpdateView::conflictColor() const
0142 {
0143     return m_conflictColor;
0144 }
0145 
0146 const QColor &UpdateView::localChangeColor() const
0147 {
0148     return m_localChangeColor;
0149 }
0150 
0151 const QColor &UpdateView::remoteChangeColor() const
0152 {
0153     return m_remoteChangeColor;
0154 }
0155 
0156 const QColor &UpdateView::notInCvsColor() const
0157 {
0158     return m_notInCvsColor;
0159 }
0160 
0161 bool UpdateView::isUnfoldingTree() const
0162 {
0163     return m_unfoldingTree;
0164 }
0165 
0166 // updates internal data
0167 void UpdateView::replaceItem(QTreeWidgetItem *oldItem, QTreeWidgetItem *newItem)
0168 {
0169     const int index(relevantSelection.indexOf(oldItem));
0170     if (index >= 0)
0171         relevantSelection.replace(index, newItem);
0172 }
0173 
0174 void UpdateView::unfoldSelectedFolders()
0175 {
0176     QApplication::setOverrideCursor(Qt::WaitCursor);
0177 
0178     int previousDepth = 0;
0179     bool isUnfolded = false;
0180 
0181     QStringList selection = multipleSelection();
0182 
0183     // setup name of selected folder
0184     QString selectedItem = selection.first();
0185     if (selectedItem.contains('/'))
0186         selectedItem.remove(0, selectedItem.lastIndexOf('/') + 1);
0187 
0188     // avoid flicker
0189     const bool _updatesEnabled = updatesEnabled();
0190     setUpdatesEnabled(false);
0191 
0192     QTreeWidgetItemIterator it(this);
0193     while (QTreeWidgetItem *item = (*it)) {
0194         if (isDirItem(item)) {
0195             auto dirItem = static_cast<UpdateDirItem *>(item);
0196 
0197             // below selected folder?
0198             if (previousDepth && dirItem->depth() > previousDepth) {
0199                 // if this dir wasn't scanned already scan it recursive
0200                 // (this is only a hack to reduce the processEvents() calls,
0201                 // setOpen() would scan the dir too)
0202                 if (dirItem->wasScanned() == false) {
0203                     const bool recursive = true;
0204                     dirItem->maybeScanDir(recursive);
0205 
0206                     // scanning can take some time so keep the gui alive
0207                     qApp->processEvents();
0208                 }
0209 
0210                 dirItem->setOpen(!isUnfolded);
0211             }
0212             // selected folder?
0213             else if (selectedItem == dirItem->entry().m_name) {
0214                 previousDepth = dirItem->depth();
0215                 isUnfolded = dirItem->isExpanded();
0216 
0217                 // if this dir wasn't scanned already scan it recursive
0218                 // (this is only a hack to reduce the processEvents() calls,
0219                 // setOpen() would scan the dir too)
0220                 if (dirItem->wasScanned() == false) {
0221                     const bool recursive = true;
0222                     dirItem->maybeScanDir(recursive);
0223 
0224                     // scanning can take some time so keep the gui alive
0225                     qApp->processEvents();
0226                 }
0227 
0228                 dirItem->setOpen(!isUnfolded);
0229             }
0230             // back to the level of the selected folder or above?
0231             else if (previousDepth && dirItem->depth() >= previousDepth) {
0232                 previousDepth = 0;
0233             }
0234         }
0235 
0236         ++it;
0237     }
0238 
0239     // maybe some UpdateDirItem was opened the first time so check the whole tree
0240     setFilter(filter());
0241 
0242     setUpdatesEnabled(_updatesEnabled);
0243     viewport()->update();
0244 
0245     QApplication::restoreOverrideCursor();
0246 }
0247 
0248 void UpdateView::unfoldTree()
0249 {
0250     QApplication::setOverrideCursor(Qt::WaitCursor);
0251 
0252     m_unfoldingTree = true;
0253 
0254     const bool _updatesEnabled = updatesEnabled();
0255 
0256     setUpdatesEnabled(false);
0257 
0258     QTreeWidgetItemIterator it(this);
0259     while (QTreeWidgetItem *item = (*it)) {
0260         if (isDirItem(item)) {
0261             auto dirItem(static_cast<UpdateDirItem *>(item));
0262 
0263             // if this dir wasn't scanned already scan it recursive
0264             // (this is only a hack to reduce the processEvents() calls,
0265             // setOpen() would scan the dir too)
0266             if (dirItem->wasScanned() == false) {
0267                 const bool recursive(true);
0268                 dirItem->maybeScanDir(recursive);
0269 
0270                 // scanning can take some time so keep the gui alive
0271                 qApp->processEvents();
0272             }
0273 
0274             dirItem->setOpen(true);
0275         }
0276 
0277         ++it;
0278     }
0279 
0280     // maybe some UpdateDirItem was opened the first time so check the whole tree
0281     setFilter(filter());
0282 
0283     setUpdatesEnabled(_updatesEnabled);
0284 
0285     viewport()->update();
0286 
0287     m_unfoldingTree = false;
0288 
0289     QApplication::restoreOverrideCursor();
0290 }
0291 
0292 void UpdateView::foldTree()
0293 {
0294     QTreeWidgetItemIterator it(this);
0295     while (QTreeWidgetItem *item = (*it)) {
0296         // don't close the top level directory
0297         if (isDirItem(item) && item->parent())
0298             item->setExpanded(false);
0299 
0300         ++it;
0301     }
0302 }
0303 
0304 /**
0305  * Clear the tree view and insert the directory dirname
0306  * into it as the new root item
0307  */
0308 void UpdateView::openDirectory(const QString &dirName)
0309 {
0310     clear();
0311 
0312     // do this each time as the configuration could be changed
0313     updateColors();
0314 
0315     Entry entry;
0316     entry.m_name = dirName;
0317     entry.m_type = Entry::Dir;
0318 
0319     auto item = new UpdateDirItem(this, entry);
0320     item->setExpanded(true);
0321     setCurrentItem(item);
0322     item->setSelected(true);
0323 }
0324 
0325 /**
0326  * Start a job. We want to be able to change the status field
0327  * correctly afterwards, so we have to remember the current
0328  * selection (which the user may change during the update).
0329  * In the recursive case, we collect all relevant directories.
0330  * Furthermore, we have to change the items to undefined state.
0331  */
0332 void UpdateView::prepareJob(bool recursive, Action action)
0333 {
0334     act = action;
0335 
0336     // Scan recursively all entries - there's no way around this here
0337     if (recursive)
0338         static_cast<UpdateDirItem *>(topLevelItem(0))->maybeScanDir(true);
0339 
0340     rememberSelection(recursive);
0341     if (act != Add)
0342         markUpdated(false, false);
0343 }
0344 
0345 /**
0346  * Finishes a job. What we do depends a bit on
0347  * whether the command was successful or not.
0348  */
0349 void UpdateView::finishJob(bool normalExit, int exitStatus)
0350 {
0351     // cvs exitStatus == 1 only means that there're conflicts
0352     // ... which is not correct (e.g. server not reachable also returns 1)
0353     const bool success(normalExit && (exitStatus == 0));
0354 
0355     if (act != Add)
0356         markUpdated(true, success);
0357     syncSelection();
0358 
0359     // maybe some new items were created or
0360     // visibility of items changed so check the whole tree
0361     setFilter(filter());
0362 }
0363 
0364 /**
0365  * Marking non-selected items in a directory updated (as a consequence
0366  * of not appearing in 'cvs update' output) is done in two steps: In the
0367  * first, they are marked as 'indefinite', so that their status on the screen
0368  * isn't misrepresented. In the second step, they are either set
0369  * to 'UpToDate' (success=true) or 'Unknown'.
0370  */
0371 void UpdateView::markUpdated(bool laststage, bool success)
0372 {
0373     foreach (QTreeWidgetItem *it, relevantSelection) {
0374         if (isDirItem(it)) {
0375             for (int i = 0; i < it->childCount(); i++) {
0376                 QTreeWidgetItem *item = it->child(i);
0377                 if (isFileItem(item)) {
0378                     auto fileItem = static_cast<UpdateFileItem *>(item);
0379                     fileItem->markUpdated(laststage, success);
0380                 }
0381             }
0382         } else {
0383             auto fileItem = static_cast<UpdateFileItem *>(it);
0384             fileItem->markUpdated(laststage, success);
0385         }
0386     }
0387 }
0388 
0389 /**
0390  * Remember the selection, see prepareJob()
0391  */
0392 void UpdateView::rememberSelection(bool recursive)
0393 {
0394     std::set<QTreeWidgetItem *> setItems;
0395     for (QTreeWidgetItemIterator it(this); *it; ++it) {
0396         QTreeWidgetItem *item(*it);
0397 
0398         // if this item is selected and if it was not inserted already
0399         // and if we work recursive and if it is a dir item then insert
0400         // all sub dirs
0401         // DON'T CHANGE TESTING ORDER
0402         if (item->isSelected() && setItems.insert(item).second && recursive && isDirItem(item)) {
0403             QStack<QTreeWidgetItem *> s;
0404             int childNum = 0;
0405             QTreeWidgetItem *startItem = item;
0406             QTreeWidgetItem *childItem = startItem->child(childNum);
0407             while (childItem) {
0408                 // if this item is a dir item and if it was not
0409                 // inserted already then insert all sub dirs
0410                 // DON'T CHANGE TESTING ORDER
0411                 if (isDirItem(childItem) && setItems.insert(childItem).second) {
0412                     if (QTreeWidgetItem *childChildItem = childItem->child(0))
0413                         s.push(childChildItem);
0414                 }
0415 
0416                 if (++childNum < startItem->childCount())
0417                     childItem = startItem->child(childNum);
0418                 else {
0419                     if (s.isEmpty())
0420                         break;
0421                     else {
0422                         childItem = s.pop();
0423                         startItem = childItem->parent();
0424                         childNum = 0;
0425                     }
0426                 }
0427             }
0428         }
0429     }
0430 
0431     // Copy the set to the list
0432     relevantSelection.clear();
0433     std::set<QTreeWidgetItem *>::const_iterator const itItemEnd = setItems.end();
0434     for (auto itItem = setItems.begin(); itItem != itItemEnd; ++itItem)
0435         relevantSelection.append(*itItem);
0436 
0437 #if 0
0438     qDebug() << "Relevant:";
0439     foreach (QTreeWidgetItem * item, relevantSelection)
0440         qDebug() << "  " << item->text(0);
0441     qDebug() << "End";
0442 #endif
0443 }
0444 
0445 /**
0446  * Use the remembered selection to resynchronize
0447  * with the actual directory and Entries content.
0448  */
0449 void UpdateView::syncSelection()
0450 {
0451     // compute all directories which are selected or contain a selected file
0452     // (in recursive mode this includes all sub directories)
0453     std::set<UpdateDirItem *> setDirItems;
0454     foreach (QTreeWidgetItem *item, relevantSelection) {
0455         UpdateDirItem *dirItem(0);
0456         if (isDirItem(item))
0457             dirItem = static_cast<UpdateDirItem *>(item);
0458         else if (QTreeWidgetItem *parentItem = item->parent())
0459             dirItem = static_cast<UpdateDirItem *>(parentItem);
0460 
0461         if (dirItem)
0462             setDirItems.insert(dirItem);
0463     }
0464 
0465     QApplication::setOverrideCursor(Qt::WaitCursor);
0466 
0467     std::set<UpdateDirItem *>::const_iterator const itDirItemEnd = setDirItems.end();
0468     for (auto itDirItem = setDirItems.begin(); itDirItem != itDirItemEnd; ++itDirItem) {
0469         UpdateDirItem *dirItem = *itDirItem;
0470 
0471         dirItem->syncWithDirectory();
0472         dirItem->syncWithEntries();
0473 
0474         qApp->processEvents();
0475     }
0476 
0477     QApplication::restoreOverrideCursor();
0478 }
0479 
0480 /**
0481  * Get the colors from the configuration each time the list view items
0482  * are created.
0483  */
0484 void UpdateView::updateColors()
0485 {
0486     KConfigGroup cs(&m_partConfig, "Colors");
0487 
0488     m_conflictColor = cs.readEntry("Conflict", QColor(255, 130, 130));
0489     m_localChangeColor = cs.readEntry("LocalChange", QColor(130, 130, 255));
0490     m_remoteChangeColor = cs.readEntry("RemoteChange", QColor(70, 210, 70));
0491 
0492     m_notInCvsColor = CervisiaSettings::notInCvsColor();
0493 }
0494 
0495 /**
0496  * Process one line from the output of 'cvs update'. If parseAsStatus
0497  * is true, it is assumed that the output is from a command
0498  * 'cvs update -n', i.e. cvs actually changes no files.
0499  */
0500 void UpdateView::processUpdateLine(QString str)
0501 {
0502     if (str.length() > 2 && str[1] == ' ') {
0503         EntryStatus status(Cervisia::Unknown);
0504         switch (str[0].toLatin1()) {
0505         case 'C':
0506             status = Cervisia::Conflict;
0507             break;
0508         case 'A':
0509             status = Cervisia::LocallyAdded;
0510             break;
0511         case 'R':
0512             status = Cervisia::LocallyRemoved;
0513             break;
0514         case 'M':
0515             status = Cervisia::LocallyModified;
0516             break;
0517         case 'U':
0518             status = (act == UpdateNoAct) ? Cervisia::NeedsUpdate : Cervisia::Updated;
0519             break;
0520         case 'P':
0521             status = (act == UpdateNoAct) ? Cervisia::NeedsPatch : Cervisia::Patched;
0522             break;
0523         case '?':
0524             status = Cervisia::NotInCVS;
0525             break;
0526         default:
0527             return;
0528         }
0529         updateItem(str.mid(2), status, false);
0530     }
0531 
0532     const QString removedFileStart(QLatin1String("cvs server: "));
0533     const QString removedFileEnd(QLatin1String(" is no longer in the repository"));
0534     if (str.startsWith(removedFileStart) && str.endsWith(removedFileEnd)) { }
0535 
0536 #if 0
0537     else if (str.left(21) == "cvs server: Updating " ||
0538              str.left(21) == "cvs update: Updating ")
0539         updateItem(str.right(str.length()-21), Unknown, true);
0540 #endif
0541 }
0542 
0543 void UpdateView::updateItem(const QString &filePath, EntryStatus status, bool isdir)
0544 {
0545     if (isdir && filePath == QLatin1String("."))
0546         return;
0547 
0548     const QFileInfo fileInfo(filePath);
0549 
0550     auto rootItem = static_cast<UpdateDirItem *>(topLevelItem(0));
0551     UpdateDirItem *dirItem = findOrCreateDirItem(fileInfo.path(), rootItem);
0552 
0553     dirItem->updateChildItem(fileInfo.fileName(), status, isdir);
0554 }
0555 
0556 void UpdateView::itemExecuted(QTreeWidgetItem *item, int)
0557 {
0558     if (isFileItem(item))
0559         emit fileOpened(static_cast<UpdateFileItem *>(item)->filePath());
0560 }
0561 
0562 void UpdateView::itemExpandedSlot(QTreeWidgetItem *item)
0563 {
0564     static_cast<UpdateItem *>(item)->setOpen(true);
0565 }
0566 
0567 // Local Variables:
0568 // c-basic-offset: 4
0569 // End: