You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
630 lines
18 KiB
630 lines
18 KiB
/*
|
|
* Copyright (C) 1999-2002 Bernd Gehrmann <bernd@mail.berlios.de>
|
|
* Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org>
|
|
*
|
|
* 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 <set>
|
|
|
|
#include <tqapplication.h>
|
|
#include <tqfileinfo.h>
|
|
#include <tqptrstack.h>
|
|
#include <kconfig.h>
|
|
#include <klocale.h>
|
|
|
|
#include "cervisiasettings.h"
|
|
#include "entry.h"
|
|
#include "updateview_items.h"
|
|
#include "updateview_visitors.h"
|
|
|
|
|
|
using Cervisia::Entry;
|
|
using Cervisia::EntryStatus;
|
|
|
|
|
|
UpdateView::UpdateView(KConfig& partConfig, TQWidget *parent, const char *name)
|
|
: KListView(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, TQT_SIGNAL(doubleClicked(TQListViewItem*)),
|
|
this, TQT_SLOT(itemExecuted(TQListViewItem*)) );
|
|
connect( this, TQT_SIGNAL(returnPressed(TQListViewItem*)),
|
|
this, TQT_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<UpdateDirItem*>(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<TQListViewItem>& listSelectedItems(selectedItems());
|
|
|
|
return (listSelectedItems.count() == 1) && isFileItem(listSelectedItems.getFirst());
|
|
}
|
|
|
|
|
|
void UpdateView::getSingleSelection(TQString *filename, TQString *revision) const
|
|
{
|
|
const TQPtrList<TQListViewItem>& listSelectedItems(selectedItems());
|
|
|
|
TQString tmpFileName;
|
|
TQString tmpRevision;
|
|
if ((listSelectedItems.count() == 1) && isFileItem(listSelectedItems.getFirst()))
|
|
{
|
|
UpdateFileItem* fileItem(static_cast<UpdateFileItem*>(listSelectedItems.getFirst()));
|
|
tmpFileName = fileItem->filePath();
|
|
tmpRevision = fileItem->entry().m_revision;
|
|
}
|
|
|
|
*filename = tmpFileName;
|
|
if (revision)
|
|
*revision = tmpRevision;
|
|
}
|
|
|
|
|
|
TQStringList UpdateView::multipleSelection() const
|
|
{
|
|
TQStringList res;
|
|
|
|
const TQPtrList<TQListViewItem>& listSelectedItems(selectedItems());
|
|
for (TQPtrListIterator<TQListViewItem> it(listSelectedItems);
|
|
it.current() != 0; ++it)
|
|
{
|
|
if ((*it)->isVisible())
|
|
res.append(static_cast<UpdateItem*>(*it)->filePath());
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
TQStringList UpdateView::fileSelection() const
|
|
{
|
|
TQStringList res;
|
|
|
|
const TQPtrList<TQListViewItem>& listSelectedItems(selectedItems());
|
|
for (TQPtrListIterator<TQListViewItem> it(listSelectedItems);
|
|
it.current() != 0; ++it)
|
|
{
|
|
TQListViewItem* item(*it);
|
|
|
|
if (isFileItem(item) && item->isVisible())
|
|
res.append(static_cast<UpdateFileItem*>(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<UpdateDirItem*>(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<UpdateDirItem*>(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<UpdateDirItem*>(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<TQListViewItem> 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<UpdateFileItem*>(item);
|
|
fileItem->markUpdated(laststage, success);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UpdateFileItem* fileItem = static_cast<UpdateFileItem*>(it.current());
|
|
fileItem->markUpdated(laststage, success);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Remember the selection, see prepareJob()
|
|
*/
|
|
void UpdateView::rememberSelection(bool recursive)
|
|
{
|
|
std::set<TQListViewItem*> 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<TQListViewItem> 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<TQListViewItem*>::const_iterator const itItemEnd = setItems.end();
|
|
for (std::set<TQListViewItem*>::const_iterator itItem = setItems.begin();
|
|
itItem != itItemEnd; ++itItem)
|
|
relevantSelection.append(*itItem);
|
|
|
|
#if 0
|
|
DEBUGOUT("Relevant:");
|
|
TQPtrListIterator<TQListViewItem> 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<UpdateDirItem*> setDirItems;
|
|
for (TQPtrListIterator<TQListViewItem> itItem(relevantSelection);
|
|
itItem.current(); ++itItem)
|
|
{
|
|
TQListViewItem* item(itItem.current());
|
|
|
|
UpdateDirItem* dirItem(0);
|
|
if (isDirItem(item))
|
|
dirItem = static_cast<UpdateDirItem*>(item);
|
|
else if (TQListViewItem* parentItem = item->parent())
|
|
dirItem = static_cast<UpdateDirItem*>(parentItem);
|
|
|
|
if (dirItem)
|
|
setDirItems.insert(dirItem);
|
|
}
|
|
|
|
TQApplication::setOverrideCursor(waitCursor);
|
|
|
|
std::set<UpdateDirItem*>::const_iterator const itDirItemEnd = setDirItems.end();
|
|
for (std::set<UpdateDirItem*>::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()
|
|
{
|
|
KConfigGroupSaver 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<UpdateDirItem*>(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<UpdateFileItem*>(item)->filePath());
|
|
}
|
|
|
|
|
|
#include "updateview.moc"
|
|
|
|
|
|
// Local Variables:
|
|
// c-basic-offset: 4
|
|
// End:
|