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.
tdesdk/cervisia/updateview_items.cpp

827 lines
22 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_items.h"
#include <cassert>
#include <tqdir.h>
#include <tqpainter.h>
#include <tqregexp.h>
#include <kdebug.h>
#include <tdeglobalsettings.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <kmimetype.h>
#include "cvsdir.h"
#include "entry.h"
#include "misc.h"
#include "updateview_visitors.h"
using Cervisia::Entry;
using Cervisia::EntryStatus;
// ------------------------------------------------------------------------------
// UpdateItem
// ------------------------------------------------------------------------------
TQString UpdateItem::dirPath() const
{
TQString path;
const UpdateItem* item = static_cast<UpdateItem*>(parent());
while (item)
{
const UpdateItem* parentItem = static_cast<UpdateItem*>(item->parent());
if (parentItem)
{
path.prepend(item->m_entry.m_name + TQDir::separator());
}
item = parentItem;
}
return path;
}
TQString UpdateItem::filePath() const
{
// the filePath of the root item is '.'
return parent() ? dirPath() + m_entry.m_name : TQChar('.');
}
// ------------------------------------------------------------------------------
// UpdateDirItem
// ------------------------------------------------------------------------------
UpdateDirItem::UpdateDirItem(UpdateDirItem* parent,
const Entry& entry)
: UpdateItem(parent, entry),
m_opened(false)
{
setExpandable(true);
setPixmap(0, SmallIcon("folder"));
}
UpdateDirItem::UpdateDirItem(UpdateView* parent,
const Entry& entry)
: UpdateItem(parent, entry),
m_opened(false)
{
setExpandable(true);
setPixmap(0, SmallIcon("folder"));
}
/**
* Update the status of an item; if it doesn't exist yet, create new one
*/
void UpdateDirItem::updateChildItem(const TQString& name,
EntryStatus status,
bool isdir)
{
if (UpdateItem* item = findItem(name))
{
if (isFileItem(item))
{
UpdateFileItem* fileItem = static_cast<UpdateFileItem*>(item);
fileItem->setStatus(status);
}
return;
}
// Not found, make new entry
Entry entry;
entry.m_name = name;
if (isdir)
{
entry.m_type = Entry::Dir;
createDirItem(entry)->maybeScanDir(true);
}
else
{
entry.m_type = Entry::File;
createFileItem(entry)->setStatus(status);
}
}
/**
* Update the revision and tag of an item. Use status only to create
* new items and for items which were NotInCVS.
*/
void UpdateDirItem::updateEntriesItem(const Entry& entry,
bool isBinary)
{
if (UpdateItem* item = findItem(entry.m_name))
{
if (isFileItem(item))
{
UpdateFileItem* fileItem = static_cast<UpdateFileItem*>(item);
if (fileItem->entry().m_status == Cervisia::NotInCVS ||
fileItem->entry().m_status == Cervisia::LocallyRemoved ||
entry.m_status == Cervisia::LocallyAdded ||
entry.m_status == Cervisia::LocallyRemoved ||
entry.m_status == Cervisia::Conflict)
{
fileItem->setStatus(entry.m_status);
}
fileItem->setRevTag(entry.m_revision, entry.m_tag);
fileItem->setDate(entry.m_dateTime);
fileItem->setPixmap(0, isBinary ? SmallIcon("application-octet-stream") : TQPixmap());
}
return;
}
// Not found, make new entry
if (entry.m_type == Entry::Dir)
createDirItem(entry)->maybeScanDir(true);
else
createFileItem(entry);
}
void UpdateDirItem::scanDirectory()
{
const TQString& path(filePath());
if (!TQFile::exists(path))
return;
const CvsDir dir(path);
const TQFileInfoList *files = dir.entryInfoList();
if (files)
{
TQFileInfoListIterator it(*files);
for (; it.current(); ++it)
{
Entry entry;
entry.m_name = it.current()->fileName();
if (it.current()->isDir())
{
entry.m_type = Entry::Dir;
createDirItem(entry);
}
else
{
entry.m_type = Entry::File;
entry.m_status = Cervisia::NotInCVS;
createFileItem(entry);
}
}
}
}
UpdateDirItem* UpdateDirItem::createDirItem(const Entry& entry)
{
UpdateItem* item(insertItem(new UpdateDirItem(this, entry)));
assert(isDirItem(item));
return static_cast<UpdateDirItem*>(item);
}
UpdateFileItem* UpdateDirItem::createFileItem(const Entry& entry)
{
UpdateItem* item(insertItem(new UpdateFileItem(this, entry)));
assert(isFileItem(item));
return static_cast<UpdateFileItem*>(item);
}
UpdateItem* UpdateDirItem::insertItem(UpdateItem* item)
{
TQPair<TMapItemsByName::iterator, bool> result
= m_itemsByName.insert(TMapItemsByName::value_type(item->entry().m_name, item));
if (!result.second)
{
// OK, an item with that name already exists. If the item type is the
// same then keep the old one to preserve it's status information
UpdateItem* existingItem = *result.first;
if (existingItem->rtti() == item->rtti())
{
delete item;
item = existingItem;
}
else
{
// avoid dangling pointers in the view
updateView()->replaceItem(existingItem, item);
delete existingItem;
*result.first = item;
}
}
return item;
}
UpdateItem* UpdateDirItem::findItem(const TQString& name) const
{
const TMapItemsByName::const_iterator it = m_itemsByName.find(name);
return (it != m_itemsByName.end()) ? *it : 0;
}
// TQt-3.3.8 changed the parsing in TQDateTime::fromString() but introduced
// a bug which leads to the problem that days with 1 digit will incorrectly being
// parsed as day 0 - which is invalid.
// workaround with the implementation from TQt-3.3.6
TQDateTime parseDateTime(const TQString &s)
{
static const char * const qt_shortMonthNames[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
TQString monthName( s.mid( 4, 3 ) );
int month = -1;
// Assume that English monthnames are the default
for ( int i = 0; i < 12; ++i ) {
if ( monthName == qt_shortMonthNames[i] ) {
month = i + 1;
break;
}
}
// If English names can't be found, search the localized ones
if ( month == -1 ) {
for ( int i = 1; i <= 12; ++i ) {
if ( monthName == TQDate::shortMonthName( i ) ) {
month = i;
break;
}
}
}
if ( month < 1 || month > 12 ) {
tqWarning( "TQDateTime::fromString: Parameter out of range" );
TQDateTime dt;
return dt;
}
int day = s.mid( 8, 2 ).simplifyWhiteSpace().toInt();
int year = s.right( 4 ).toInt();
TQDate date( year, month, day );
TQTime time;
int hour, minute, second;
int pivot = s.find( TQRegExp(TQString::fromLatin1("[0-9][0-9]:[0-9][0-9]:[0-9][0-9]")) );
if ( pivot != -1 ) {
hour = s.mid( pivot, 2 ).toInt();
minute = s.mid( pivot+3, 2 ).toInt();
second = s.mid( pivot+6, 2 ).toInt();
time.setHMS( hour, minute, second );
}
return TQDateTime( date, time );
}
// Format of the CVS/Entries file:
// /NAME/REVISION/[CONFLICT+]TIMESTAMP/OPTIONS/TAGDATE
void UpdateDirItem::syncWithEntries()
{
const TQString path(filePath() + TQDir::separator());
TQFile f(path + "CVS/Entries");
if( f.open(IO_ReadOnly) )
{
TQTextStream stream(&f);
while( !stream.eof() )
{
TQString line = stream.readLine();
Cervisia::Entry entry;
const bool isDir(line[0] == 'D');
if( isDir )
line.remove(0, 1);
if( line[0] != '/' )
continue;
entry.m_type = isDir ? Entry::Dir : Entry::File;
entry.m_name = line.section('/', 1, 1);
if (isDir)
{
updateEntriesItem(entry, false);
}
else
{
TQString rev(line.section('/', 2, 2));
const TQString timestamp(line.section('/', 3, 3));
const TQString options(line.section('/', 4, 4));
entry.m_tag = line.section('/', 5, 5);
const bool isBinary(options.find("-kb") >= 0);
// file date in local time
entry.m_dateTime = TQFileInfo(path + entry.m_name).lastModified();
if( rev == "0" )
entry.m_status = Cervisia::LocallyAdded;
else if( rev.length() > 2 && rev[0] == '-' )
{
entry.m_status = Cervisia::LocallyRemoved;
rev.remove(0, 1);
}
else if (timestamp.find('+') >= 0)
{
entry.m_status = Cervisia::Conflict;
}
else
{
// workaround TQt-3.3.8 bug with our own function (see function above)
// const TQDateTime date(TQDateTime::fromString(timestamp)); // UTC Time
const TQDateTime date(parseDateTime(timestamp)); // UTC Time
TQDateTime fileDateUTC;
fileDateUTC.setTime_t(entry.m_dateTime.toTime_t(), TQt::UTC);
if (date != fileDateUTC)
entry.m_status = Cervisia::LocallyModified;
}
entry.m_revision = rev;
updateEntriesItem(entry, isBinary);
}
}
}
}
/**
* Test if files was removed from repository.
*/
void UpdateDirItem::syncWithDirectory()
{
TQDir dir(filePath());
for (TMapItemsByName::iterator it(m_itemsByName.begin()),
itEnd(m_itemsByName.end());
it != itEnd; ++it)
{
// only files
if (isFileItem(*it))
{
UpdateFileItem* fileItem = static_cast<UpdateFileItem*>(*it);
// is file removed?
if (!dir.exists(it.key()))
{
fileItem->setStatus(Cervisia::Removed);
fileItem->setRevTag(TQString(), TQString());
}
}
}
}
/**
* Read in the content of the directory. If recursive is false, this
* is shallow, otherwise all child directories are scanned recursively.
*/
void UpdateDirItem::maybeScanDir(bool recursive)
{
if (!m_opened)
{
m_opened = true;
scanDirectory();
syncWithEntries();
// sort the created items
sort();
}
if (recursive)
{
for (TMapItemsByName::iterator it(m_itemsByName.begin()),
itEnd(m_itemsByName.end());
it != itEnd; ++it)
{
if (isDirItem(*it))
static_cast<UpdateDirItem*>(*it)->maybeScanDir(true);
}
}
}
void UpdateDirItem::accept(Visitor& visitor)
{
visitor.preVisit(this);
for (TMapItemsByName::iterator it(m_itemsByName.begin()),
itEnd(m_itemsByName.end());
it != itEnd; ++it)
{
(*it)->accept(visitor);
}
visitor.postVisit(this);
}
void UpdateDirItem::setOpen(bool open)
{
if ( open )
{
const bool openFirstTime(!wasScanned());
maybeScanDir(false);
// if new items were created their visibility must be checked
// (not while unfoldTree() as this could be slow and unfoldTree()
// calls setFilter() itself)
UpdateView* view = updateView();
if (openFirstTime && !view->isUnfoldingTree())
view->setFilter(view->filter());
}
TQListViewItem::setOpen(open);
}
int UpdateDirItem::compare(TQListViewItem* i,
int /*column*/,
bool bAscending) const
{
// UpdateDirItems are always lesser than UpdateFileItems
if (isFileItem(i))
return bAscending ? -1 : 1;
const UpdateDirItem* item(static_cast<UpdateDirItem*>(i));
// for every column just compare the directory name
return entry().m_name.localeAwareCompare(item->entry().m_name);
}
TQString UpdateDirItem::text(int column) const
{
TQString result;
if (column == Name)
result = entry().m_name;
return result;
}
// ------------------------------------------------------------------------------
// UpdateFileItem
// ------------------------------------------------------------------------------
UpdateFileItem::UpdateFileItem(UpdateDirItem* parent, const Entry& entry)
: UpdateItem(parent, entry),
m_undefined(false)
{
}
void UpdateFileItem::setStatus(EntryStatus status)
{
if (status != m_entry.m_status)
{
m_entry.m_status = status;
const bool visible(applyFilter(updateView()->filter()));
if (visible)
repaint();
}
m_undefined = false;
}
void UpdateFileItem::accept(Visitor& visitor)
{
visitor.visit(this);
}
bool UpdateFileItem::applyFilter(UpdateView::Filter filter)
{
bool visible(true);
if (filter & UpdateView::OnlyDirectories)
visible = false;
bool unmodified = (entry().m_status == Cervisia::UpToDate) ||
(entry().m_status == Cervisia::Unknown);
if ((filter & UpdateView::NoUpToDate) && unmodified)
visible = false;
if ((filter & UpdateView::NoRemoved) && (entry().m_status == Cervisia::Removed))
visible = false;
if ((filter & UpdateView::NoNotInCVS) && (entry().m_status == Cervisia::NotInCVS))
visible = false;
setVisible(visible);
return visible;
}
void UpdateFileItem::setRevTag(const TQString& rev, const TQString& tag)
{
m_entry.m_revision = rev;
if (tag.length() == 20 && tag[0] == 'D' && tag[5] == '.'
&& tag[8] == '.' && tag[11] == '.' && tag[14] == '.'
&& tag[17] == '.')
{
const TQDate tagDate(tag.mid(1, 4).toInt(),
tag.mid(6, 2).toInt(),
tag.mid(9, 2).toInt());
const TQTime tagTime(tag.mid(12, 2).toInt(),
tag.mid(15, 2).toInt(),
tag.mid(18, 2).toInt());
const TQDateTime tagDateTimeUtc(tagDate, tagTime);
if (tagDateTimeUtc.isValid())
{
// This is in UTC and must be converted to local time.
//
// A bit strange but I didn't find anything easier which is portable.
// Compute the difference between UTC and local timezone for this
// tag date.
const unsigned int dateTimeInSeconds(tagDateTimeUtc.toTime_t());
TQDateTime dateTime;
dateTime.setTime_t(dateTimeInSeconds, TQt::UTC);
const int localUtcOffset(dateTime.secsTo(tagDateTimeUtc));
const TQDateTime tagDateTimeLocal(tagDateTimeUtc.addSecs(localUtcOffset));
m_entry.m_tag = TDEGlobal::locale()->formatDateTime(tagDateTimeLocal);
}
else
m_entry.m_tag = tag;
}
else if (tag.length() > 1 && tag[0] == 'T')
m_entry.m_tag = tag.mid(1);
else
m_entry.m_tag = tag;
if (isVisible())
{
widthChanged();
repaint();
}
}
void UpdateFileItem::setDate(const TQDateTime& date)
{
m_entry.m_dateTime = date;
}
void UpdateFileItem::markUpdated(bool laststage,
bool success)
{
EntryStatus newstatus = m_entry.m_status;
if (laststage)
{
if (undefinedState() && m_entry.m_status != Cervisia::NotInCVS)
newstatus = success? Cervisia::UpToDate : Cervisia::Unknown;
setStatus(newstatus);
}
else
setUndefinedState(true);
}
int UpdateFileItem::statusClass() const
{
int iResult(0);
switch (entry().m_status)
{
case Cervisia::Conflict:
iResult = 0;
break;
case Cervisia::LocallyAdded:
iResult = 1;
break;
case Cervisia::LocallyRemoved:
iResult = 2;
break;
case Cervisia::LocallyModified:
iResult = 3;
break;
case Cervisia::Updated:
case Cervisia::NeedsUpdate:
case Cervisia::Patched:
case Cervisia::Removed:
case Cervisia::NeedsPatch:
case Cervisia::NeedsMerge:
iResult = 4;
break;
case Cervisia::NotInCVS:
iResult = 5;
break;
case Cervisia::UpToDate:
case Cervisia::Unknown:
iResult = 6;
break;
}
return iResult;
}
int UpdateFileItem::compare(TQListViewItem* i,
int column,
bool bAscending) const
{
// UpdateDirItems are always lesser than UpdateFileItems
if (isDirItem(i))
return bAscending ? 1 : -1;
const UpdateFileItem* item = static_cast<UpdateFileItem*>(i);
int iResult(0);
switch (column)
{
case Name:
iResult = entry().m_name.localeAwareCompare(item->entry().m_name);
break;
case MimeType:
iResult = KMimeType::findByPath(entry().m_name)->comment().localeAwareCompare(KMimeType::findByPath(item->entry().m_name)->comment());
break;
case Status:
if ((iResult = ::compare(statusClass(), item->statusClass())) == 0)
iResult = entry().m_name.localeAwareCompare(item->entry().m_name);
break;
case Revision:
iResult = ::compareRevisions(entry().m_revision, item->entry().m_revision);
break;
case TagOrDate:
iResult = entry().m_tag.localeAwareCompare(item->entry().m_tag);
break;
case Timestamp:
iResult = ::compare(entry().m_dateTime, item->entry().m_dateTime);
break;
}
return iResult;
}
TQString UpdateFileItem::text(int column) const
{
TQString result;
switch (column)
{
case Name:
result = entry().m_name;
break;
case MimeType:
result = KMimeType::findByPath(entry().m_name)->comment();
break;
case Status:
result = toString(entry().m_status);
break;
case Revision:
result = entry().m_revision;
break;
case TagOrDate:
result = entry().m_tag;
break;
case Timestamp:
if (entry().m_dateTime.isValid())
result = TDEGlobal::locale()->formatDateTime(entry().m_dateTime);
break;
}
return result;
}
void UpdateFileItem::paintCell(TQPainter *p,
const TQColorGroup &cg,
int col,
int width,
int align)
{
const UpdateView* view(updateView());
TQColor color;
switch (m_entry.m_status)
{
case Cervisia::Conflict:
color = view->conflictColor();
break;
case Cervisia::LocallyAdded:
case Cervisia::LocallyModified:
case Cervisia::LocallyRemoved:
color = view->localChangeColor();
break;
case Cervisia::NeedsMerge:
case Cervisia::NeedsPatch:
case Cervisia::NeedsUpdate:
case Cervisia::Patched:
case Cervisia::Removed:
case Cervisia::Updated:
color = view->remoteChangeColor();
break;
case Cervisia::NotInCVS:
color = view->notInCvsColor();
break;
case Cervisia::Unknown:
case Cervisia::UpToDate:
break;
}
const TQFont oldFont(p->font());
TQColorGroup mycg(cg);
if (color.isValid() && color != TDEGlobalSettings::textColor())
{
TQFont myFont(oldFont);
myFont.setBold(true);
p->setFont(myFont);
mycg.setColor(TQColorGroup::Text, color);
}
TQListViewItem::paintCell(p, mycg, col, width, align);
if (color.isValid())
{
p->setFont(oldFont);
}
}
/**
* Finds or creates the UpdateDirItem with path \a dirPath. If \a dirPath
* is "." \a rootItem is returned.
*/
UpdateDirItem* findOrCreateDirItem(const TQString& dirPath,
UpdateDirItem* rootItem)
{
assert(!dirPath.isEmpty());
assert(rootItem);
UpdateDirItem* dirItem(rootItem);
if (dirPath != TQChar('.'))
{
const TQStringList& dirNames(TQStringList::split('/', dirPath));
const TQStringList::const_iterator itDirNameEnd(dirNames.end());
for (TQStringList::const_iterator itDirName(dirNames.begin());
itDirName != itDirNameEnd; ++itDirName)
{
const TQString& dirName(*itDirName);
UpdateItem* item = dirItem->findItem(dirName);
if (isFileItem(item))
{
// this happens if you
// - add a directory outside of Cervisia
// - update status (a file item is created for the directory)
// - add new directory in Cervisia
// - update status
kdDebug(8050) << "findOrCreateDirItem(): file changed to dir " << dirName << endl;
// just create a new dir item, createDirItem() will delete the
// file item and update the m_itemsByName map
item = 0;
}
if (!item)
{
kdDebug(8050) << "findOrCreateDirItem(): create dir item " << dirName << endl;
Entry entry;
entry.m_name = dirName;
entry.m_type = Entry::Dir;
item = dirItem->createDirItem(entry);
}
assert(isDirItem(item));
dirItem = static_cast<UpdateDirItem*>(item);
}
}
return dirItem;
}