Mercurial > hg > easyhg
diff src/filestatuswidget.cpp @ 370:b9c153e00e84
Move source files to src/
author | Chris Cannam |
---|---|
date | Thu, 24 Mar 2011 10:27:51 +0000 |
parents | filestatuswidget.cpp@4cd753e083cc |
children | b6d36a17899d |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/filestatuswidget.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,538 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#include "filestatuswidget.h" +#include "debug.h" +#include "multichoicedialog.h" + +#include <QLabel> +#include <QListWidget> +#include <QGridLayout> +#include <QFileInfo> +#include <QApplication> +#include <QDateTime> +#include <QPushButton> +#include <QToolButton> +#include <QDir> +#include <QProcess> +#include <QCheckBox> +#include <QSettings> +#include <QAction> + +FileStatusWidget::FileStatusWidget(QWidget *parent) : + QWidget(parent), + m_dateReference(0) +{ + QGridLayout *layout = new QGridLayout; + layout->setMargin(10); + setLayout(layout); + + int row = 0; + + m_noModificationsLabel = new QLabel; + setNoModificationsLabelText(); + layout->addWidget(m_noModificationsLabel, row, 0); + m_noModificationsLabel->hide(); + + m_simpleLabels[FileStates::Clean] = tr("Unmodified:"); + m_simpleLabels[FileStates::Modified] = tr("Modified:"); + m_simpleLabels[FileStates::Added] = tr("Added:"); + m_simpleLabels[FileStates::Removed] = tr("Removed:"); + m_simpleLabels[FileStates::Missing] = tr("Missing:"); + m_simpleLabels[FileStates::InConflict] = tr("In Conflict:"); + m_simpleLabels[FileStates::Unknown] = tr("Untracked:"); + m_simpleLabels[FileStates::Ignored] = tr("Ignored:"); + + m_actionLabels[FileStates::Annotate] = tr("Show annotated version"); + m_actionLabels[FileStates::Diff] = tr("Diff to parent"); + m_actionLabels[FileStates::Commit] = tr("Commit..."); + m_actionLabels[FileStates::Revert] = tr("Revert to last committed state"); + m_actionLabels[FileStates::Rename] = tr("Rename..."); + m_actionLabels[FileStates::Copy] = tr("Copy..."); + m_actionLabels[FileStates::Add] = tr("Add to version control"); + m_actionLabels[FileStates::Remove] = tr("Remove from version control"); + m_actionLabels[FileStates::RedoMerge] = tr("Redo merge"); + m_actionLabels[FileStates::MarkResolved] = tr("Mark conflict as resolved"); + m_actionLabels[FileStates::Ignore] = tr("Ignore"); + m_actionLabels[FileStates::UnIgnore] = tr("Stop ignoring"); + + m_descriptions[FileStates::Clean] = tr("You have not changed these files."); + m_descriptions[FileStates::Modified] = tr("You have changed these files since you last committed them."); + m_descriptions[FileStates::Added] = tr("These files will be added to version control next time you commit them."); + m_descriptions[FileStates::Removed] = tr("These files will be removed from version control next time you commit them.<br>" + "They will not be deleted from the local folder."); + m_descriptions[FileStates::Missing] = tr("These files are recorded in the version control, but absent from your working folder.<br>" + "If you intended to delete them, select them and use Remove to tell the version control system about it.<br>" + "If you deleted them by accident, select them and use Revert to restore their previous contents."); + m_descriptions[FileStates::InConflict] = tr("These files are unresolved following an incomplete merge.<br>Select a file and use Merge to try to resolve the merge again."); + m_descriptions[FileStates::Unknown] = tr("These files are in your working folder but are not under version control.<br>" +// "Select a file and use Add to place it under version control or Ignore to remove it from this list."); + "Select a file and use Add to place it under version control."); + m_descriptions[FileStates::Ignored] = tr("These files have names that match entries in the working folder's .hgignore file,<br>" + "and so will be ignored by the version control system."); + + m_highlightExplanation = tr("Files highlighted <font color=#d40000>in red</font> " + "have appeared since your most recent commit or update."); + + m_boxesParent = new QWidget(this); + layout->addWidget(m_boxesParent, ++row, 0); + + QGridLayout *boxesLayout = new QGridLayout; + boxesLayout->setMargin(0); + m_boxesParent->setLayout(boxesLayout); + int boxRow = 0; + + for (int i = int(FileStates::FirstState); + i <= int(FileStates::LastState); ++i) { + + FileStates::State s = FileStates::State(i); + + QWidget *box = new QWidget(m_boxesParent); + QGridLayout *boxlayout = new QGridLayout; + boxlayout->setMargin(0); + box->setLayout(boxlayout); + + boxlayout->addItem(new QSpacerItem(3, 3), 0, 0); + + QLabel *label = new QLabel(labelFor(s)); + label->setWordWrap(true); + boxlayout->addWidget(label, 1, 0); + + QListWidget *w = new QListWidget; + m_stateListMap[s] = w; + w->setSelectionMode(QListWidget::ExtendedSelection); + boxlayout->addWidget(w, 2, 0); + + connect(w, SIGNAL(itemSelectionChanged()), + this, SLOT(itemSelectionChanged())); + connect(w, SIGNAL(itemDoubleClicked(QListWidgetItem *)), + this, SLOT(itemDoubleClicked(QListWidgetItem *))); + + FileStates::Activities activities = m_fileStates.activitiesSupportedBy(s); + int prevGroup = -1; + foreach (FileStates::Activity a, activities) { + // Skip activities which are not yet implemented + if (a == FileStates::Ignore || + a == FileStates::UnIgnore) { + continue; + } + int group = FileStates::activityGroup(a); + if (group != prevGroup && prevGroup != -1) { + QAction *sep = new QAction("", w); + sep->setSeparator(true); + w->insertAction(0, sep); + } + prevGroup = group; + QAction *act = new QAction(m_actionLabels[a], w); + act->setProperty("state", s); + act->setProperty("activity", a); + connect(act, SIGNAL(triggered()), this, SLOT(menuActionActivated())); + w->insertAction(0, act); + } + w->setContextMenuPolicy(Qt::ActionsContextMenu); + + boxlayout->addItem(new QSpacerItem(2, 2), 3, 0); + + boxesLayout->addWidget(box, ++boxRow, 0); + m_boxes.push_back(box); + box->hide(); + } + + m_gridlyLayout = false; + + layout->setRowStretch(++row, 20); + + layout->addItem(new QSpacerItem(8, 8), ++row, 0); + + m_showAllFiles = new QCheckBox(tr("Show all files"), this); + m_showAllFiles->setEnabled(false); + layout->addWidget(m_showAllFiles, ++row, 0, Qt::AlignLeft); + connect(m_showAllFiles, SIGNAL(toggled(bool)), + this, SIGNAL(showAllChanged(bool))); +} + +FileStatusWidget::~FileStatusWidget() +{ + delete m_dateReference; +} + +QString FileStatusWidget::labelFor(FileStates::State s, bool addHighlightExplanation) +{ + QSettings settings; + settings.beginGroup("Presentation"); + if (settings.value("showhelpfultext", true).toBool()) { + if (addHighlightExplanation) { + return QString("<qt><b>%1</b><br>%2<br>%3</qt>") + .arg(m_simpleLabels[s]) + .arg(m_descriptions[s]) + .arg(m_highlightExplanation); + } else { + return QString("<qt><b>%1</b><br>%2</qt>") + .arg(m_simpleLabels[s]) + .arg(m_descriptions[s]); + } + } else { + return QString("<qt><b>%1</b></qt>") + .arg(m_simpleLabels[s]); + } + settings.endGroup(); +} + +void FileStatusWidget::setNoModificationsLabelText() +{ + QSettings settings; + settings.beginGroup("Presentation"); + if (settings.value("showhelpfultext", true).toBool()) { + m_noModificationsLabel->setText + (tr("<qt>This area will list files in your working folder that you have changed.<br><br>At the moment you have no uncommitted changes.<br><br>To see changes previously made to the repository,<br>switch to the History tab.<br><br>%1</qt>") +#if defined Q_OS_MAC + .arg(tr("To open the working folder in Finder,<br>click on the “Local” folder path shown above.")) +#elif defined Q_OS_WIN32 + .arg(tr("To open the working folder in Windows Explorer,<br>click on the “Local” folder path shown above.")) +#else + .arg(tr("To open the working folder in your system file manager,<br>click the “Local” folder path shown above.")) +#endif + ); + } else { + m_noModificationsLabel->setText + (tr("<qt>You have no uncommitted changes.</qt>")); + } +} + + +void FileStatusWidget::menuActionActivated() +{ + QAction *act = qobject_cast<QAction *>(sender()); + if (!act) return; + + FileStates::State state = (FileStates::State) + act->property("state").toUInt(); + FileStates::Activity activity = (FileStates::Activity) + act->property("activity").toUInt(); + + DEBUG << "menuActionActivated: state = " << state << ", activity = " + << activity << endl; + + if (!FileStates::supportsActivity(state, activity)) { + std::cerr << "WARNING: FileStatusWidget::menuActionActivated: " + << "Action state " << state << " does not support activity " + << activity << std::endl; + return; + } + + QStringList files = getSelectedFilesInState(state); + + switch (activity) { + case FileStates::Annotate: emit annotateFiles(files); break; + case FileStates::Diff: emit diffFiles(files); break; + case FileStates::Commit: emit commitFiles(files); break; + case FileStates::Revert: emit revertFiles(files); break; + case FileStates::Rename: emit renameFiles(files); break; + case FileStates::Copy: emit copyFiles(files); break; + case FileStates::Add: emit addFiles(files); break; + case FileStates::Remove: emit removeFiles(files); break; + case FileStates::RedoMerge: emit redoFileMerges(files); break; + case FileStates::MarkResolved: emit markFilesResolved(files); break; + case FileStates::Ignore: emit ignoreFiles(files); break; + case FileStates::UnIgnore: emit unIgnoreFiles(files); break; + } +} + +void FileStatusWidget::itemDoubleClicked(QListWidgetItem *item) +{ + QStringList files; + QString file = item->text(); + files << file; + + switch (m_fileStates.stateOf(file)) { + + case FileStates::Modified: + case FileStates::InConflict: + emit diffFiles(files); + break; + + case FileStates::Clean: + case FileStates::Missing: + emit annotateFiles(files); + break; + } +} + +void FileStatusWidget::itemSelectionChanged() +{ + DEBUG << "FileStatusWidget::itemSelectionChanged" << endl; + + QListWidget *list = qobject_cast<QListWidget *>(sender()); + + if (list) { + foreach (QListWidget *w, m_stateListMap) { + if (w != list) { + w->blockSignals(true); + w->clearSelection(); + w->blockSignals(false); + } + } + } + + m_selectedFiles.clear(); + + foreach (QListWidget *w, m_stateListMap) { + QList<QListWidgetItem *> sel = w->selectedItems(); + foreach (QListWidgetItem *i, sel) { + m_selectedFiles.push_back(i->text()); + DEBUG << "file " << i->text() << " is selected" << endl; + } + } + + emit selectionChanged(); +} + +void FileStatusWidget::clearSelections() +{ + m_selectedFiles.clear(); + foreach (QListWidget *w, m_stateListMap) { + w->clearSelection(); + } +} + +bool FileStatusWidget::haveChangesToCommit() const +{ + return !getAllCommittableFiles().empty(); +} + +bool FileStatusWidget::haveSelection() const +{ + return !m_selectedFiles.empty(); +} + +QStringList FileStatusWidget::getSelectedFilesInState(FileStates::State s) const +{ + QStringList files; + foreach (QString f, m_selectedFiles) { + if (m_fileStates.stateOf(f) == s) files.push_back(f); + } + return files; +} + +QStringList FileStatusWidget::getSelectedFilesSupportingActivity(FileStates::Activity a) const +{ + QStringList files; + foreach (QString f, m_selectedFiles) { + if (m_fileStates.supportsActivity(f, a)) files.push_back(f); + } + return files; +} + +QStringList FileStatusWidget::getAllCommittableFiles() const +{ + return m_fileStates.filesSupportingActivity(FileStates::Commit); +} + +QStringList FileStatusWidget::getAllRevertableFiles() const +{ + return m_fileStates.filesSupportingActivity(FileStates::Revert); +} + +QStringList FileStatusWidget::getAllUnresolvedFiles() const +{ + return m_fileStates.filesInState(FileStates::InConflict); +} + +QStringList FileStatusWidget::getSelectedAddableFiles() const +{ + return getSelectedFilesSupportingActivity(FileStates::Add); +} + +QStringList FileStatusWidget::getSelectedRemovableFiles() const +{ + return getSelectedFilesSupportingActivity(FileStates::Remove); +} + +QString +FileStatusWidget::localPath() const +{ + return m_localPath; +} + +void +FileStatusWidget::setLocalPath(QString p) +{ + m_localPath = p; + delete m_dateReference; + m_dateReference = new QFileInfo(p + "/.hg/dirstate"); + if (!m_dateReference->exists() || + !m_dateReference->isFile() || + !m_dateReference->isReadable()) { + DEBUG << "FileStatusWidget::setLocalPath: date reference file " + << m_dateReference->absoluteFilePath() + << " does not exist, is not a file, or cannot be read" + << endl; + delete m_dateReference; + m_dateReference = 0; + m_showAllFiles->setEnabled(false); + } else { + m_showAllFiles->setEnabled(true); + } +} + +void +FileStatusWidget::setFileStates(FileStates p) +{ + m_fileStates = p; + updateWidgets(); +} + +void +FileStatusWidget::updateWidgets() +{ + QDateTime lastInteractionTime; + if (m_dateReference) { + lastInteractionTime = m_dateReference->lastModified(); + DEBUG << "reference time: " << lastInteractionTime << endl; + } + + QSet<QString> selectedFiles; + foreach (QString f, m_selectedFiles) selectedFiles.insert(f); + + int visibleCount = 0; + + foreach (FileStates::State s, m_stateListMap.keys()) { + + QListWidget *w = m_stateListMap[s]; + w->clear(); + QStringList files = m_fileStates.filesInState(s); + + QStringList highPriority, lowPriority; + + foreach (QString file, files) { + + bool highlighted = false; + + if (s == FileStates::Unknown) { + // We want to highlight untracked files that have appeared + // since the last interaction with the repo + QString fn(m_localPath + "/" + file); + DEBUG << "comparing with " << fn << endl; + QFileInfo fi(fn); + if (fi.exists() && fi.created() > lastInteractionTime) { + DEBUG << "file " << fn << " is newer (" << fi.lastModified() + << ") than reference" << endl; + highlighted = true; + } + } + + if (highlighted) { + highPriority.push_back(file); + } else { + lowPriority.push_back(file); + } + } + + foreach (QString file, highPriority) { + QListWidgetItem *item = new QListWidgetItem(file); + w->addItem(item); + item->setForeground(QColor("#d40000")); + item->setSelected(selectedFiles.contains(file)); + } + + foreach (QString file, lowPriority) { + QListWidgetItem *item = new QListWidgetItem(file); + w->addItem(item); + item->setSelected(selectedFiles.contains(file)); + } + + setLabelFor(w, s, !highPriority.empty()); + + if (files.empty()) { + w->parentWidget()->hide(); + } else { + w->parentWidget()->show(); + ++visibleCount; + } + } + + m_noModificationsLabel->setVisible(visibleCount == 0); + + if (visibleCount > 3) { + layoutBoxesGridly(visibleCount); + } else { + layoutBoxesLinearly(); + } + + setNoModificationsLabelText(); +} + +void FileStatusWidget::layoutBoxesGridly(int visibleCount) +{ + if (m_gridlyLayout && m_lastGridlyCount == visibleCount) return; + + delete m_boxesParent->layout(); + + QGridLayout *layout = new QGridLayout; + layout->setMargin(0); + m_boxesParent->setLayout(layout); + + int row = 0; + int col = 0; + + DEBUG << "FileStatusWidget::layoutBoxesGridly: visibleCount = " + << visibleCount << endl; + + for (int i = 0; i < m_boxes.size(); ++i) { + + if (!m_boxes[i]->isVisible()) continue; + + if (col == 0 && row >= (visibleCount+1)/2) { + layout->addItem(new QSpacerItem(10, 5), 0, 1); + col = 2; + row = 0; + } + + layout->addWidget(m_boxes[i], row, col); + + ++row; + } + + m_gridlyLayout = true; + m_lastGridlyCount = visibleCount; +} + +void FileStatusWidget::layoutBoxesLinearly() +{ + if (!m_gridlyLayout) return; + + delete m_boxesParent->layout(); + + QGridLayout *layout = new QGridLayout; + layout->setMargin(0); + m_boxesParent->setLayout(layout); + + for (int i = 0; i < m_boxes.size(); ++i) { + layout->addWidget(m_boxes[i], i, 0); + } + + m_gridlyLayout = false; +} + +void FileStatusWidget::setLabelFor(QWidget *w, FileStates::State s, bool addHighlight) +{ + QString text = labelFor(s, addHighlight); + QWidget *p = w->parentWidget(); + QList<QLabel *> ql = p->findChildren<QLabel *>(); + if (!ql.empty()) ql[0]->setText(text); +} +