summaryrefslogtreecommitdiffstats
path: root/cervisia/updateview_items.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'cervisia/updateview_items.cpp')
-rw-r--r--cervisia/updateview_items.cpp826
1 files changed, 826 insertions, 0 deletions
diff --git a/cervisia/updateview_items.cpp b/cervisia/updateview_items.cpp
new file mode 100644
index 0000000..76223c2
--- /dev/null
+++ b/cervisia/updateview_items.cpp
@@ -0,0 +1,826 @@
+/*
+ * 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 <qdir.h>
+#include <qpainter.h>
+#include <qregexp.h>
+
+#include <kdebug.h>
+#include <kglobalsettings.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kmimetype.h>
+
+#include "cvsdir.h"
+#include "entry.h"
+#include "misc.h"
+#include "updateview_visitors.h"
+
+
+using Cervisia::Entry;
+using Cervisia::EntryStatus;
+
+
+// ------------------------------------------------------------------------------
+// UpdateItem
+// ------------------------------------------------------------------------------
+
+
+QString UpdateItem::dirPath() const
+{
+ QString 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 + QDir::separator());
+ }
+
+ item = parentItem;
+ }
+
+ return path;
+}
+
+
+QString UpdateItem::filePath() const
+{
+ // the filePath of the root item is '.'
+ return parent() ? dirPath() + m_entry.m_name : QChar('.');
+}
+
+
+// ------------------------------------------------------------------------------
+// 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 QString& 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("binary") : QPixmap());
+ }
+ return;
+ }
+
+ // Not found, make new entry
+ if (entry.m_type == Entry::Dir)
+ createDirItem(entry)->maybeScanDir(true);
+ else
+ createFileItem(entry);
+}
+
+
+void UpdateDirItem::scanDirectory()
+{
+ const QString& path(filePath());
+ if (!QFile::exists(path))
+ return;
+
+ const CvsDir dir(path);
+
+ const QFileInfoList *files = dir.entryInfoList();
+ if (files)
+ {
+ QFileInfoListIterator 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)
+{
+ QPair<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 QString& name) const
+{
+ const TMapItemsByName::const_iterator it = m_itemsByName.find(name);
+
+ return (it != m_itemsByName.end()) ? *it : 0;
+}
+
+// Qt-3.3.8 changed the parsing in QDateTime::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 Qt-3.3.6
+QDateTime parseDateTime(const QString &s)
+{
+ static const char * const qt_shortMonthNames[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+ QString 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 == QDate::shortMonthName( i ) ) {
+ month = i;
+ break;
+ }
+ }
+ }
+ if ( month < 1 || month > 12 ) {
+ qWarning( "QDateTime::fromString: Parameter out of range" );
+ QDateTime dt;
+ return dt;
+ }
+ int day = s.mid( 8, 2 ).simplifyWhiteSpace().toInt();
+ int year = s.right( 4 ).toInt();
+ QDate date( year, month, day );
+ QTime time;
+ int hour, minute, second;
+ int pivot = s.find( QRegExp(QString::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 QDateTime( date, time );
+}
+
+// Format of the CVS/Entries file:
+// /NAME/REVISION/[CONFLICT+]TIMESTAMP/OPTIONS/TAGDATE
+
+void UpdateDirItem::syncWithEntries()
+{
+ const QString path(filePath() + QDir::separator());
+
+ QFile f(path + "CVS/Entries");
+ if( f.open(IO_ReadOnly) )
+ {
+ QTextStream stream(&f);
+ while( !stream.eof() )
+ {
+ QString 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
+ {
+ QString rev(line.section('/', 2, 2));
+ const QString timestamp(line.section('/', 3, 3));
+ const QString 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 = QFileInfo(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 Qt-3.3.8 bug with our own function (see function above)
+ // const QDateTime date(QDateTime::fromString(timestamp)); // UTC Time
+ const QDateTime date(parseDateTime(timestamp)); // UTC Time
+ QDateTime fileDateUTC;
+ fileDateUTC.setTime_t(entry.m_dateTime.toTime_t(), Qt::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()
+{
+ QDir 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(QString::null, QString::null);
+ }
+ }
+ }
+}
+
+
+/**
+ * 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());
+ }
+
+ QListViewItem::setOpen(open);
+}
+
+
+int UpdateDirItem::compare(QListViewItem* 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);
+}
+
+
+QString UpdateDirItem::text(int column) const
+{
+ QString 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 QString& rev, const QString& tag)
+{
+ m_entry.m_revision = rev;
+
+ if (tag.length() == 20 && tag[0] == 'D' && tag[5] == '.'
+ && tag[8] == '.' && tag[11] == '.' && tag[14] == '.'
+ && tag[17] == '.')
+ {
+ const QDate tagDate(tag.mid(1, 4).toInt(),
+ tag.mid(6, 2).toInt(),
+ tag.mid(9, 2).toInt());
+ const QTime tagTime(tag.mid(12, 2).toInt(),
+ tag.mid(15, 2).toInt(),
+ tag.mid(18, 2).toInt());
+ const QDateTime 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());
+ QDateTime dateTime;
+ dateTime.setTime_t(dateTimeInSeconds, Qt::UTC);
+ const int localUtcOffset(dateTime.secsTo(tagDateTimeUtc));
+
+ const QDateTime tagDateTimeLocal(tagDateTimeUtc.addSecs(localUtcOffset));
+
+ m_entry.m_tag = KGlobal::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 QDateTime& 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(QListViewItem* 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;
+}
+
+
+QString UpdateFileItem::text(int column) const
+{
+ QString 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 = KGlobal::locale()->formatDateTime(entry().m_dateTime);
+ break;
+ }
+
+ return result;
+}
+
+
+void UpdateFileItem::paintCell(QPainter *p,
+ const QColorGroup &cg,
+ int col,
+ int width,
+ int align)
+{
+ const UpdateView* view(updateView());
+
+ QColor 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 QFont oldFont(p->font());
+ QColorGroup mycg(cg);
+ if (color.isValid() && color != KGlobalSettings::textColor())
+ {
+ QFont myFont(oldFont);
+ myFont.setBold(true);
+ p->setFont(myFont);
+ mycg.setColor(QColorGroup::Text, color);
+ }
+
+ QListViewItem::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 QString& dirPath,
+ UpdateDirItem* rootItem)
+{
+ assert(!dirPath.isEmpty());
+ assert(rootItem);
+
+ UpdateDirItem* dirItem(rootItem);
+
+ if (dirPath != QChar('.'))
+ {
+ const QStringList& dirNames(QStringList::split('/', dirPath));
+ const QStringList::const_iterator itDirNameEnd(dirNames.end());
+ for (QStringList::const_iterator itDirName(dirNames.begin());
+ itDirName != itDirNameEnd; ++itDirName)
+ {
+ const QString& 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;
+}