/* * Copyright (C) 1999-2002 Bernd Gehrmann * Copyright (c) 2003-2007 André Wöbbeking * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "updateview.h" #include #include #include #include #include #include #include "cervisiasettings.h" #include "entry.h" #include "updateview_items.h" #include "updateview_visitors.h" using Cervisia::Entry; using Cervisia::EntryStatus; UpdateView::UpdateView(TDEConfig& partConfig, TQWidget *parent, const char *name) : TDEListView(parent, name), m_partConfig(partConfig), m_unfoldingTree(false) { setAllColumnsShowFocus(true); setShowSortIndicator(true); setSelectionModeExt(Extended); addColumn(i18n("File Name"), 280); addColumn(i18n("File Type"), 180); addColumn(i18n("Status"), 90); addColumn(i18n("Revision"), 70); addColumn(i18n("Tag/Date"), 90); addColumn(i18n("Timestamp"), 120); setFilter(NoFilter); connect( this, TQ_SIGNAL(doubleClicked(TQListViewItem*)), this, TQ_SLOT(itemExecuted(TQListViewItem*)) ); connect( this, TQ_SIGNAL(returnPressed(TQListViewItem*)), this, TQ_SLOT(itemExecuted(TQListViewItem*)) ); // without this restoreLayout() can't change the column widths for (int col = 0; col < columns(); ++col) setColumnWidthMode(col, TQListView::Manual); restoreLayout(&m_partConfig, TQString::fromLatin1("UpdateView")); } UpdateView::~UpdateView() { saveLayout(&m_partConfig, TQString::fromLatin1("UpdateView")); } void UpdateView::setFilter(Filter filter) { filt = filter; if (UpdateDirItem* item = static_cast(firstChild())) { ApplyFilterVisitor applyFilterVisitor(filter); item->accept(applyFilterVisitor); } setSorting(columnSorted(), ascendingSort()); } UpdateView::Filter UpdateView::filter() const { return filt; } // returns true iff exactly one UpdateFileItem is selected bool UpdateView::hasSingleSelection() const { const TQPtrList& listSelectedItems(selectedItems()); return (listSelectedItems.count() == 1) && isFileItem(listSelectedItems.getFirst()); } void UpdateView::getSingleSelection(TQString *filename, TQString *revision) const { const TQPtrList& listSelectedItems(selectedItems()); TQString tmpFileName; TQString tmpRevision; if ((listSelectedItems.count() == 1) && isFileItem(listSelectedItems.getFirst())) { UpdateFileItem* fileItem(static_cast(listSelectedItems.getFirst())); tmpFileName = fileItem->filePath(); tmpRevision = fileItem->entry().m_revision; } *filename = tmpFileName; if (revision) *revision = tmpRevision; } TQStringList UpdateView::multipleSelection() const { TQStringList res; const TQPtrList& listSelectedItems(selectedItems()); for (TQPtrListIterator it(listSelectedItems); it.current() != 0; ++it) { if ((*it)->isVisible()) res.append(static_cast(*it)->filePath()); } return res; } TQStringList UpdateView::fileSelection() const { TQStringList res; const TQPtrList& listSelectedItems(selectedItems()); for (TQPtrListIterator it(listSelectedItems); it.current() != 0; ++it) { TQListViewItem* item(*it); if (isFileItem(item) && item->isVisible()) res.append(static_cast(item)->filePath()); } return res; } const TQColor& UpdateView::conflictColor() const { return m_conflictColor; } const TQColor& UpdateView::localChangeColor() const { return m_localChangeColor; } const TQColor& UpdateView::remoteChangeColor() const { return m_remoteChangeColor; } const TQColor& UpdateView::notInCvsColor() const { return m_notInCvsColor; } bool UpdateView::isUnfoldingTree() const { return m_unfoldingTree; } // updates internal data void UpdateView::replaceItem(TQListViewItem* oldItem, TQListViewItem* newItem) { const int index(relevantSelection.find(oldItem)); if (index >= 0) relevantSelection.replace(index, newItem); } void UpdateView::unfoldSelectedFolders() { TQApplication::setOverrideCursor(waitCursor); int previousDepth = 0; bool isUnfolded = false; TQStringList selection = multipleSelection(); // setup name of selected folder TQString selectedItem = selection.first(); if( selectedItem.contains('/') ) selectedItem.remove(0, selectedItem.findRev('/')+1); // avoid flicker const bool updatesEnabled = isUpdatesEnabled(); setUpdatesEnabled(false); TQListViewItemIterator it(this); while( TQListViewItem* item = it.current() ) { if( isDirItem(item) ) { UpdateDirItem* dirItem = static_cast(item); // below selected folder? if( previousDepth && dirItem->depth() > previousDepth ) { // if this dir wasn't scanned already scan it recursive // (this is only a hack to reduce the processEvents() calls, // setOpen() would scan the dir too) if (dirItem->wasScanned() == false) { const bool recursive = true; dirItem->maybeScanDir(recursive); // scanning can take some time so keep the gui alive tqApp->processEvents(); } dirItem->setOpen(!isUnfolded); } // selected folder? else if( selectedItem == dirItem->entry().m_name ) { previousDepth = dirItem->depth(); isUnfolded = dirItem->isOpen(); // if this dir wasn't scanned already scan it recursive // (this is only a hack to reduce the processEvents() calls, // setOpen() would scan the dir too) if (dirItem->wasScanned() == false) { const bool recursive = true; dirItem->maybeScanDir(recursive); // scanning can take some time so keep the gui alive tqApp->processEvents(); } dirItem->setOpen(!isUnfolded); } // back to the level of the selected folder or above? else if( previousDepth && dirItem->depth() >= previousDepth ) { previousDepth = 0; } } ++it; } // maybe some UpdateDirItem was opened the first time so check the whole tree setFilter(filter()); setUpdatesEnabled(updatesEnabled); triggerUpdate(); TQApplication::restoreOverrideCursor(); } void UpdateView::unfoldTree() { TQApplication::setOverrideCursor(waitCursor); m_unfoldingTree = true; const bool updatesEnabled(isUpdatesEnabled()); setUpdatesEnabled(false); TQListViewItemIterator it(this); while (TQListViewItem* item = it.current()) { if (isDirItem(item)) { UpdateDirItem* dirItem(static_cast(item)); // if this dir wasn't scanned already scan it recursive // (this is only a hack to reduce the processEvents() calls, // setOpen() would scan the dir too) if (dirItem->wasScanned() == false) { const bool recursive(true); dirItem->maybeScanDir(recursive); // scanning can take some time so keep the gui alive tqApp->processEvents(); } dirItem->setOpen(true); } ++it; } // maybe some UpdateDirItem was opened the first time so check the whole tree setFilter(filter()); setUpdatesEnabled(updatesEnabled); triggerUpdate(); m_unfoldingTree = false; TQApplication::restoreOverrideCursor(); } void UpdateView::foldTree() { TQListViewItemIterator it(this); while (TQListViewItem* item = it.current()) { // don't close the top level directory if (isDirItem(item) && item->parent()) item->setOpen(false); ++it; } } /** * Clear the tree view and insert the directory dirname * into it as the new root item */ void UpdateView::openDirectory(const TQString& dirName) { clear(); // do this each time as the configuration could be changed updateColors(); Entry entry; entry.m_name = dirName; entry.m_type = Entry::Dir; UpdateDirItem *item = new UpdateDirItem(this, entry); item->setOpen(true); setCurrentItem(item); setSelected(item, true); } /** * Start a job. We want to be able to change the status field * correctly afterwards, so we have to remember the current * selection (which the user may change during the update). * In the recursive case, we collect all relevant directories. * Furthermore, we have to change the items to undefined state. */ void UpdateView::prepareJob(bool recursive, Action action) { act = action; // Scan recursively all entries - there's no way around this here if (recursive) static_cast(firstChild())->maybeScanDir(true); rememberSelection(recursive); if (act != Add) markUpdated(false, false); } /** * Finishes a job. What we do depends a bit on * whether the command was successful or not. */ void UpdateView::finishJob(bool normalExit, int exitStatus) { // cvs exitStatus == 1 only means that there're conflicts const bool success(normalExit && (exitStatus == 0 || exitStatus == 1)); if (act != Add) markUpdated(true, success); syncSelection(); // maybe some new items were created or // visibility of items changed so check the whole tree setFilter(filter()); } /** * Marking non-selected items in a directory updated (as a consequence * of not appearing in 'cvs update' output) is done in two steps: In the * first, they are marked as 'indefinite', so that their status on the screen * isn't misrepresented. In the second step, they are either set * to 'UpToDate' (success=true) or 'Unknown'. */ void UpdateView::markUpdated(bool laststage, bool success) { TQPtrListIterator it(relevantSelection); for ( ; it.current(); ++it) if (isDirItem(it.current())) { for (TQListViewItem *item = it.current()->firstChild(); item; item = item->nextSibling() ) if (isFileItem(item)) { UpdateFileItem* fileItem = static_cast(item); fileItem->markUpdated(laststage, success); } } else { UpdateFileItem* fileItem = static_cast(it.current()); fileItem->markUpdated(laststage, success); } } /** * Remember the selection, see prepareJob() */ void UpdateView::rememberSelection(bool recursive) { std::set setItems; for (TQListViewItemIterator it(this); it.current(); ++it) { TQListViewItem* item(it.current()); // if this item is selected and if it was not inserted already // and if we work recursive and if it is a dir item then insert // all sub dirs // DON'T CHANGE TESTING ORDER if (item->isSelected() && setItems.insert(item).second && recursive && isDirItem(item)) { TQPtrStack s; for (TQListViewItem* childItem = item->firstChild(); childItem; childItem = childItem->nextSibling() ? childItem->nextSibling() : s.pop()) { // if this item is a dir item and if it is was not // inserted already then insert all sub dirs // DON'T CHANGE TESTING ORDER if (isDirItem(childItem) && setItems.insert(childItem).second) { if (TQListViewItem* childChildItem = childItem->firstChild()) s.push(childChildItem); } } } } // Copy the set to the list relevantSelection.clear(); std::set::const_iterator const itItemEnd = setItems.end(); for (std::set::const_iterator itItem = setItems.begin(); itItem != itItemEnd; ++itItem) relevantSelection.append(*itItem); #if 0 DEBUGOUT("Relevant:"); TQPtrListIterator it44(relevantSelection); for (; it44.current(); ++it44) DEBUGOUT(" " << (*it44)->text(UpdateFileItem::File)); DEBUGOUT("End"); #endif } /** * Use the remembered selection to resynchronize * with the actual directory and Entries content. */ void UpdateView::syncSelection() { // compute all directories which are selected or contain a selected file // (in recursive mode this includes all sub directories) std::set setDirItems; for (TQPtrListIterator itItem(relevantSelection); itItem.current(); ++itItem) { TQListViewItem* item(itItem.current()); UpdateDirItem* dirItem(0); if (isDirItem(item)) dirItem = static_cast(item); else if (TQListViewItem* parentItem = item->parent()) dirItem = static_cast(parentItem); if (dirItem) setDirItems.insert(dirItem); } TQApplication::setOverrideCursor(waitCursor); std::set::const_iterator const itDirItemEnd = setDirItems.end(); for (std::set::const_iterator itDirItem = setDirItems.begin(); itDirItem != itDirItemEnd; ++itDirItem) { UpdateDirItem* dirItem = *itDirItem; dirItem->syncWithDirectory(); dirItem->syncWithEntries(); tqApp->processEvents(); } TQApplication::restoreOverrideCursor(); } /** * Get the colors from the configuration each time the list view items * are created. */ void UpdateView::updateColors() { TDEConfigGroupSaver cs(&m_partConfig, "Colors"); m_partConfig.setGroup("Colors"); TQColor defaultColor = TQColor(255, 130, 130); m_conflictColor = m_partConfig.readColorEntry("Conflict", &defaultColor); defaultColor = TQColor(130, 130, 255); m_localChangeColor = m_partConfig.readColorEntry("LocalChange", &defaultColor); defaultColor = TQColor(70, 210, 70); m_remoteChangeColor = m_partConfig.readColorEntry("RemoteChange", &defaultColor); m_notInCvsColor = CervisiaSettings::notInCvsColor(); } /** * Process one line from the output of 'cvs update'. If parseAsStatus * is true, it is assumed that the output is from a command * 'cvs update -n', i.e. cvs actually changes no files. */ void UpdateView::processUpdateLine(TQString str) { if (str.length() > 2 && str[1] == ' ') { EntryStatus status(Cervisia::Unknown); switch (str[0].latin1()) { case 'C': status = Cervisia::Conflict; break; case 'A': status = Cervisia::LocallyAdded; break; case 'R': status = Cervisia::LocallyRemoved; break; case 'M': status = Cervisia::LocallyModified; break; case 'U': status = (act == UpdateNoAct) ? Cervisia::NeedsUpdate : Cervisia::Updated; break; case 'P': status = (act == UpdateNoAct) ? Cervisia::NeedsPatch : Cervisia::Patched; break; case '?': status = Cervisia::NotInCVS; break; default: return; } updateItem(str.mid(2), status, false); } const TQString removedFileStart(TQString::fromLatin1("cvs server: ")); const TQString removedFileEnd(TQString::fromLatin1(" is no longer in the repository")); if (str.startsWith(removedFileStart) && str.endsWith(removedFileEnd)) { } #if 0 else if (str.left(21) == "cvs server: Updating " || str.left(21) == "cvs update: Updating ") updateItem(str.right(str.length()-21), Unknown, true); #endif } void UpdateView::updateItem(const TQString& filePath, EntryStatus status, bool isdir) { if (isdir && filePath == TQChar('.')) return; const TQFileInfo fileInfo(filePath); UpdateDirItem* rootItem = static_cast(firstChild()); UpdateDirItem* dirItem = findOrCreateDirItem(fileInfo.dirPath(), rootItem); dirItem->updateChildItem(fileInfo.fileName(), status, isdir); } void UpdateView::itemExecuted(TQListViewItem *item) { if (isFileItem(item)) emit fileOpened(static_cast(item)->filePath()); } #include "updateview.moc"