/* * 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_items.h" #include #include #include #include #include #include #include #include #include #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(parent()); while (item) { const UpdateItem* parentItem = static_cast(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(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(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(item); } UpdateFileItem* UpdateDirItem::createFileItem(const Entry& entry) { UpdateItem* item(insertItem(new UpdateFileItem(this, entry))); assert(isFileItem(item)); return static_cast(item); } UpdateItem* UpdateDirItem::insertItem(UpdateItem* item) { TQPair 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(*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(*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(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(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(item); } } return dirItem; }