annotate filestatuswidget.cpp @ 199:f16fe0db11f3

* Add "Show All Files" toggle to show ignored and clean files * Clear selection when Esc is pressed * Don't delete and recreate the filesystem watcher on stat, just update it
author Chris Cannam
date Mon, 03 Jan 2011 22:02:08 +0000
parents 869825bc8bc4
children 0844b4d8d911 3d4291d4226c
rev   line source
Chris@88 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@88 2
Chris@88 3 /*
Chris@88 4 EasyMercurial
Chris@88 5
Chris@88 6 Based on HgExplorer by Jari Korhonen
Chris@88 7 Copyright (c) 2010 Jari Korhonen
Chris@88 8 Copyright (c) 2010 Chris Cannam
Chris@88 9 Copyright (c) 2010 Queen Mary, University of London
Chris@88 10
Chris@88 11 This program is free software; you can redistribute it and/or
Chris@88 12 modify it under the terms of the GNU General Public License as
Chris@88 13 published by the Free Software Foundation; either version 2 of the
Chris@88 14 License, or (at your option) any later version. See the file
Chris@88 15 COPYING included with this distribution for more information.
Chris@88 16 */
Chris@88 17
Chris@88 18 #include "filestatuswidget.h"
Chris@115 19 #include "debug.h"
Chris@115 20 #include "multichoicedialog.h"
Chris@186 21 #include "clickablelabel.h"
Chris@88 22
Chris@88 23 #include <QLabel>
Chris@88 24 #include <QListWidget>
Chris@88 25 #include <QGridLayout>
Chris@93 26 #include <QFileInfo>
Chris@93 27 #include <QApplication>
Chris@93 28 #include <QDateTime>
Chris@115 29 #include <QPushButton>
Chris@186 30 #include <QToolButton>
Chris@186 31 #include <QDir>
Chris@186 32 #include <QProcess>
Chris@199 33 #include <QCheckBox>
Chris@88 34
Chris@88 35 FileStatusWidget::FileStatusWidget(QWidget *parent) :
Chris@93 36 QWidget(parent),
Chris@93 37 m_dateReference(0)
Chris@88 38 {
Chris@88 39 QGridLayout *layout = new QGridLayout;
Chris@88 40 setLayout(layout);
Chris@88 41
Chris@88 42 int row = 0;
Chris@88 43
Chris@115 44 layout->addItem(new QSpacerItem(1, 1), row, 0);
Chris@115 45
Chris@115 46 ++row;
Chris@88 47 layout->addWidget(new QLabel(tr("Local:")), row, 0);
Chris@186 48
Chris@186 49 m_openButton = new ClickableLabel;
Chris@186 50 QFont f(m_openButton->font());
Chris@89 51 f.setBold(true);
Chris@186 52 m_openButton->setFont(f);
Chris@186 53 m_openButton->setMouseUnderline(true);
Chris@186 54 connect(m_openButton, SIGNAL(clicked()), this, SLOT(openButtonClicked()));
Chris@187 55 layout->addWidget(m_openButton, row, 1, 1, 2, Qt::AlignLeft);
Chris@88 56
Chris@88 57 ++row;
Chris@88 58 layout->addWidget(new QLabel(tr("Remote:")), row, 0);
Chris@88 59 m_remoteURLLabel = new QLabel;
Chris@186 60 layout->addWidget(m_remoteURLLabel, row, 1, 1, 2);
Chris@88 61
Chris@106 62 ++row;
Chris@115 63 layout->addWidget(new QLabel(tr("State:")), row, 0);
Chris@115 64 m_stateLabel = new QLabel;
Chris@115 65 layout->addWidget(m_stateLabel, row, 1, 1, 2);
Chris@106 66
Chris@89 67 layout->setColumnStretch(1, 20);
Chris@88 68
Chris@115 69 layout->addWidget(new QLabel("<qt><hr></qt>"), ++row, 0, 1, 3);
Chris@115 70
Chris@115 71 ++row;
Chris@115 72 m_noModificationsLabel = new QLabel
Chris@162 73 (tr("You have no uncommitted changes."));
Chris@115 74 layout->addWidget(m_noModificationsLabel, row, 1, 1, 2);
Chris@106 75
Chris@100 76 m_simpleLabels[FileStates::Clean] = tr("Unmodified:");
Chris@100 77 m_simpleLabels[FileStates::Modified] = tr("Modified:");
Chris@100 78 m_simpleLabels[FileStates::Added] = tr("Added:");
Chris@100 79 m_simpleLabels[FileStates::Removed] = tr("Removed:");
Chris@100 80 m_simpleLabels[FileStates::Missing] = tr("Missing:");
Chris@163 81 m_simpleLabels[FileStates::InConflict] = tr("In Conflict:");
Chris@100 82 m_simpleLabels[FileStates::Unknown] = tr("Untracked:");
Chris@199 83 m_simpleLabels[FileStates::Ignored] = tr("Ignored:");
Chris@99 84
Chris@100 85 m_descriptions[FileStates::Clean] = tr("You have not changed these files.");
Chris@100 86 m_descriptions[FileStates::Modified] = tr("You have changed these files since you last committed them.");
Chris@100 87 m_descriptions[FileStates::Added] = tr("These files will be added to version control next time you commit.");
Chris@100 88 m_descriptions[FileStates::Removed] = tr("These files will be removed from version control next time you commit.<br>"
Chris@100 89 "They will not be deleted from the local folder.");
Chris@109 90 m_descriptions[FileStates::Missing] = tr("These files are recorded in the version control, but absent from your working folder.<br>"
Chris@153 91 "If you intended to delete them, select them and use Remove to tell the version control system about it.<br>"
Chris@153 92 "If you deleted them by accident, select them and use Revert to restore their previous contents.");
Chris@163 93 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.");
Chris@100 94 m_descriptions[FileStates::Unknown] = tr("These files are in your working folder but are not under version control.<br>"
Chris@186 95 // "Select a file and use Add to place it under version control or Ignore to remove it from this list.");
Chris@186 96 "Select a file and use Add to place it under version control.");
Chris@199 97 m_descriptions[FileStates::Ignored] = tr("These files have names that match entries in the working folder's .hgignore file,<br>"
Chris@199 98 "and so will be ignored by the version control system.");
Chris@100 99
Chris@118 100 m_highlightExplanation = tr("Files highlighted <font color=#d40000>in red</font> "
Chris@100 101 "have appeared since your most recent commit or update.");
Chris@89 102
Chris@94 103 for (int i = int(FileStates::FirstState);
Chris@163 104 i <= int(FileStates::LastState); ++i) {
Chris@89 105
Chris@94 106 FileStates::State s = FileStates::State(i);
Chris@89 107
Chris@89 108 QWidget *box = new QWidget;
Chris@89 109 QGridLayout *boxlayout = new QGridLayout;
Chris@99 110 boxlayout->setMargin(0);
Chris@89 111 box->setLayout(boxlayout);
Chris@89 112
Chris@106 113 boxlayout->addItem(new QSpacerItem(5, 5), 0, 0);
Chris@101 114
Chris@101 115 boxlayout->addWidget(new QLabel(labelFor(s)), 1, 0);
Chris@89 116
Chris@94 117 QListWidget *w = new QListWidget;
Chris@94 118 m_stateListMap[s] = w;
Chris@94 119 w->setSelectionMode(QListWidget::ExtendedSelection);
Chris@101 120 boxlayout->addWidget(w, 2, 0);
Chris@89 121
Chris@95 122 connect(w, SIGNAL(itemSelectionChanged()),
Chris@95 123 this, SLOT(itemSelectionChanged()));
Chris@95 124
Chris@115 125 layout->addWidget(box, ++row, 0, 1, 3);
Chris@89 126 box->hide();
Chris@89 127 }
Chris@89 128
Chris@89 129 layout->setRowStretch(++row, 20);
Chris@186 130
Chris@199 131 layout->addItem(new QSpacerItem(1, 1), ++row, 0);
Chris@199 132
Chris@199 133 m_showAllFiles = new QCheckBox(tr("Show all files"), this);
Chris@199 134 layout->addWidget(m_showAllFiles, ++row, 0, 1, 3, Qt::AlignLeft);
Chris@199 135 connect(m_showAllFiles, SIGNAL(toggled(bool)),
Chris@199 136 this, SIGNAL(showAllChanged(bool)));
Chris@88 137 }
Chris@88 138
Chris@93 139 FileStatusWidget::~FileStatusWidget()
Chris@93 140 {
Chris@93 141 delete m_dateReference;
Chris@93 142 }
Chris@93 143
Chris@186 144 void FileStatusWidget::openButtonClicked()
Chris@186 145 {
Chris@186 146 QDir d(m_localPath);
Chris@186 147 if (d.exists()) {
Chris@186 148 QStringList args;
Chris@186 149 args << d.canonicalPath();
Chris@186 150 QProcess::execute(
Chris@186 151 #if defined Q_OS_WIN32
Chris@186 152 "c:/windows/explorer.exe",
Chris@186 153 #elif defined Q_OS_MAC
Chris@186 154 "/usr/bin/open",
Chris@186 155 #else
Chris@186 156 "/usr/bin/xdg-open",
Chris@186 157 #endif
Chris@186 158 args);
Chris@186 159 }
Chris@186 160 }
Chris@186 161
Chris@100 162 QString FileStatusWidget::labelFor(FileStates::State s, bool addHighlightExplanation)
Chris@100 163 {
Chris@100 164 if (addHighlightExplanation) {
Chris@100 165 return QString("<qt><b>%1</b><br>%2<br>%3</qt>")
Chris@100 166 .arg(m_simpleLabels[s])
Chris@100 167 .arg(m_descriptions[s])
Chris@100 168 .arg(m_highlightExplanation);
Chris@100 169 } else {
Chris@100 170 return QString("<qt><b>%1</b><br>%2</qt>")
Chris@100 171 .arg(m_simpleLabels[s])
Chris@100 172 .arg(m_descriptions[s]);
Chris@100 173 }
Chris@100 174 }
Chris@100 175
Chris@95 176 void FileStatusWidget::itemSelectionChanged()
Chris@95 177 {
Chris@135 178 DEBUG << "FileStatusWidget::itemSelectionChanged" << endl;
Chris@135 179
Chris@135 180 QListWidget *list = qobject_cast<QListWidget *>(sender());
Chris@135 181
Chris@135 182 if (list) {
Chris@135 183 foreach (QListWidget *w, m_stateListMap) {
Chris@135 184 if (w != list) {
Chris@135 185 w->blockSignals(true);
Chris@135 186 w->clearSelection();
Chris@135 187 w->blockSignals(false);
Chris@135 188 }
Chris@135 189 }
Chris@135 190 }
Chris@135 191
Chris@95 192 m_selectedFiles.clear();
Chris@95 193
Chris@95 194 foreach (QListWidget *w, m_stateListMap) {
Chris@95 195 QList<QListWidgetItem *> sel = w->selectedItems();
Chris@95 196 foreach (QListWidgetItem *i, sel) {
Chris@95 197 m_selectedFiles.push_back(i->text());
Chris@95 198 DEBUG << "file " << i->text() << " is selected" << endl;
Chris@95 199 }
Chris@95 200 }
Chris@95 201
Chris@95 202 emit selectionChanged();
Chris@95 203 }
Chris@95 204
Chris@94 205 void FileStatusWidget::clearSelections()
Chris@94 206 {
Chris@95 207 m_selectedFiles.clear();
Chris@94 208 foreach (QListWidget *w, m_stateListMap) {
Chris@94 209 w->clearSelection();
Chris@94 210 }
Chris@94 211 }
Chris@94 212
Chris@95 213 bool FileStatusWidget::haveChangesToCommit() const
Chris@95 214 {
Chris@95 215 return !m_fileStates.added().empty() ||
Chris@95 216 !m_fileStates.removed().empty() ||
Chris@95 217 !m_fileStates.modified().empty();
Chris@95 218 }
Chris@95 219
Chris@95 220 bool FileStatusWidget::haveSelection() const
Chris@95 221 {
Chris@95 222 return !m_selectedFiles.empty();
Chris@95 223 }
Chris@95 224
Chris@95 225 QStringList FileStatusWidget::getAllSelectedFiles() const
Chris@95 226 {
Chris@95 227 return m_selectedFiles;
Chris@95 228 }
Chris@95 229
Chris@95 230 QStringList FileStatusWidget::getSelectedCommittableFiles() const
Chris@95 231 {
Chris@95 232 QStringList files;
Chris@95 233 foreach (QString f, m_selectedFiles) {
Chris@95 234 switch (m_fileStates.getStateOfFile(f)) {
Chris@95 235 case FileStates::Added:
Chris@95 236 case FileStates::Modified:
Chris@95 237 case FileStates::Removed:
Chris@95 238 files.push_back(f);
Chris@95 239 break;
Chris@95 240 default: break;
Chris@95 241 }
Chris@95 242 }
Chris@95 243 return files;
Chris@95 244 }
Chris@95 245
Chris@103 246 QStringList FileStatusWidget::getAllCommittableFiles() const
Chris@103 247 {
Chris@103 248 QStringList files;
Chris@103 249 files << m_fileStates.getFilesInState(FileStates::Modified);
Chris@103 250 files << m_fileStates.getFilesInState(FileStates::Added);
Chris@103 251 files << m_fileStates.getFilesInState(FileStates::Removed);
Chris@103 252 return files;
Chris@103 253 }
Chris@103 254
Chris@109 255 QStringList FileStatusWidget::getSelectedRevertableFiles() const
Chris@109 256 {
Chris@109 257 QStringList files;
Chris@109 258 foreach (QString f, m_selectedFiles) {
Chris@109 259 switch (m_fileStates.getStateOfFile(f)) {
Chris@109 260 case FileStates::Added:
Chris@109 261 case FileStates::Modified:
Chris@109 262 case FileStates::Removed:
Chris@109 263 case FileStates::Missing:
Chris@163 264 case FileStates::InConflict:
Chris@109 265 files.push_back(f);
Chris@109 266 break;
Chris@109 267 default: break;
Chris@109 268 }
Chris@109 269 }
Chris@109 270 return files;
Chris@109 271 }
Chris@109 272
Chris@109 273 QStringList FileStatusWidget::getAllRevertableFiles() const
Chris@109 274 {
Chris@109 275 QStringList files;
Chris@109 276 files << m_fileStates.getFilesInState(FileStates::Modified);
Chris@109 277 files << m_fileStates.getFilesInState(FileStates::Added);
Chris@109 278 files << m_fileStates.getFilesInState(FileStates::Removed);
Chris@109 279 files << m_fileStates.getFilesInState(FileStates::Missing);
Chris@163 280 files << m_fileStates.getFilesInState(FileStates::InConflict);
Chris@163 281 return files;
Chris@163 282 }
Chris@163 283
Chris@163 284 QStringList FileStatusWidget::getSelectedUnresolvedFiles() const
Chris@163 285 {
Chris@163 286 QStringList files;
Chris@163 287 foreach (QString f, m_selectedFiles) {
Chris@163 288 switch (m_fileStates.getStateOfFile(f)) {
Chris@163 289 case FileStates::InConflict:
Chris@163 290 files.push_back(f);
Chris@163 291 break;
Chris@163 292 default: break;
Chris@163 293 }
Chris@163 294 }
Chris@163 295 return files;
Chris@163 296 }
Chris@163 297
Chris@163 298 QStringList FileStatusWidget::getAllUnresolvedFiles() const
Chris@163 299 {
Chris@163 300 QStringList files;
Chris@163 301 files << m_fileStates.getFilesInState(FileStates::InConflict);
Chris@109 302 return files;
Chris@109 303 }
Chris@109 304
Chris@95 305 QStringList FileStatusWidget::getSelectedAddableFiles() const
Chris@95 306 {
Chris@95 307 QStringList files;
Chris@95 308 foreach (QString f, m_selectedFiles) {
Chris@95 309 switch (m_fileStates.getStateOfFile(f)) {
Chris@95 310 case FileStates::Unknown:
Chris@95 311 case FileStates::Removed:
Chris@95 312 files.push_back(f);
Chris@95 313 break;
Chris@95 314 default: break;
Chris@95 315 }
Chris@95 316 }
Chris@95 317 return files;
Chris@95 318 }
Chris@95 319
Chris@103 320 QStringList FileStatusWidget::getAllAddableFiles() const
Chris@103 321 {
Chris@103 322 QStringList files;
Chris@103 323 files << m_fileStates.getFilesInState(FileStates::Removed);
Chris@103 324 files << m_fileStates.getFilesInState(FileStates::Unknown);
Chris@103 325 return files;
Chris@103 326 }
Chris@103 327
Chris@95 328 QStringList FileStatusWidget::getSelectedRemovableFiles() const
Chris@95 329 {
Chris@95 330 QStringList files;
Chris@95 331 foreach (QString f, m_selectedFiles) {
Chris@95 332 switch (m_fileStates.getStateOfFile(f)) {
Chris@95 333 case FileStates::Clean:
Chris@95 334 case FileStates::Added:
Chris@95 335 case FileStates::Modified:
Chris@95 336 case FileStates::Missing:
Chris@163 337 case FileStates::InConflict:
Chris@95 338 files.push_back(f);
Chris@95 339 break;
Chris@95 340 default: break;
Chris@95 341 }
Chris@95 342 }
Chris@95 343 return files;
Chris@95 344 }
Chris@95 345
Chris@103 346 QStringList FileStatusWidget::getAllRemovableFiles() const
Chris@103 347 {
Chris@103 348 QStringList files;
Chris@103 349 files << m_fileStates.getFilesInState(FileStates::Clean);
Chris@103 350 files << m_fileStates.getFilesInState(FileStates::Added);
Chris@103 351 files << m_fileStates.getFilesInState(FileStates::Modified);
Chris@103 352 files << m_fileStates.getFilesInState(FileStates::Missing);
Chris@163 353 files << m_fileStates.getFilesInState(FileStates::InConflict);
Chris@103 354 return files;
Chris@103 355 }
Chris@103 356
Chris@88 357 void
Chris@88 358 FileStatusWidget::setLocalPath(QString p)
Chris@88 359 {
Chris@88 360 m_localPath = p;
Chris@186 361 m_openButton->setText(p);
Chris@93 362 delete m_dateReference;
Chris@93 363 m_dateReference = new QFileInfo(p + "/.hg/dirstate");
Chris@93 364 if (!m_dateReference->exists() ||
Chris@93 365 !m_dateReference->isFile() ||
Chris@93 366 !m_dateReference->isReadable()) {
Chris@93 367 DEBUG << "FileStatusWidget::setLocalPath: date reference file "
Chris@93 368 << m_dateReference->absoluteFilePath()
Chris@93 369 << " does not exist, is not a file, or cannot be read"
Chris@93 370 << endl;
Chris@93 371 delete m_dateReference;
Chris@93 372 m_dateReference = 0;
Chris@93 373 }
Chris@186 374 m_openButton->setEnabled(QDir(m_localPath).exists());
Chris@88 375 }
Chris@88 376
Chris@88 377 void
Chris@88 378 FileStatusWidget::setRemoteURL(QString r)
Chris@88 379 {
Chris@88 380 m_remoteURL = r;
Chris@88 381 m_remoteURLLabel->setText(r);
Chris@88 382 }
Chris@88 383
Chris@88 384 void
Chris@92 385 FileStatusWidget::setFileStates(FileStates p)
Chris@88 386 {
Chris@92 387 m_fileStates = p;
Chris@88 388 updateWidgets();
Chris@88 389 }
Chris@88 390
Chris@88 391 void
Chris@115 392 FileStatusWidget::setState(QString b)
Chris@106 393 {
Chris@115 394 m_state = b;
Chris@115 395 updateStateLabel();
Chris@106 396 }
Chris@106 397
Chris@106 398 void
Chris@88 399 FileStatusWidget::updateWidgets()
Chris@88 400 {
Chris@95 401 QDateTime lastInteractionTime;
Chris@95 402 if (m_dateReference) {
Chris@95 403 lastInteractionTime = m_dateReference->lastModified();
Chris@95 404 DEBUG << "reference time: " << lastInteractionTime << endl;
Chris@95 405 }
Chris@95 406
Chris@95 407 QSet<QString> selectedFiles;
Chris@95 408 foreach (QString f, m_selectedFiles) selectedFiles.insert(f);
Chris@95 409
Chris@115 410 bool haveAnything = false;
Chris@115 411
Chris@94 412 foreach (FileStates::State s, m_stateListMap.keys()) {
Chris@95 413
Chris@94 414 QListWidget *w = m_stateListMap[s];
Chris@94 415 w->clear();
Chris@95 416 QStringList files = m_fileStates.getFilesInState(s);
Chris@93 417
Chris@95 418 QStringList highPriority, lowPriority;
Chris@95 419
Chris@95 420 foreach (QString file, files) {
Chris@95 421
Chris@95 422 bool highlighted = false;
Chris@95 423
Chris@95 424 if (s == FileStates::Unknown) {
Chris@95 425 // We want to highlight untracked files that have appeared
Chris@95 426 // since the last interaction with the repo
Chris@95 427 QString fn(m_localPath + "/" + file);
Chris@95 428 DEBUG << "comparing with " << fn << endl;
Chris@95 429 QFileInfo fi(fn);
Chris@100 430 if (fi.exists() && fi.created() > lastInteractionTime) {
Chris@95 431 DEBUG << "file " << fn << " is newer (" << fi.lastModified()
Chris@95 432 << ") than reference" << endl;
Chris@95 433 highlighted = true;
Chris@95 434 }
Chris@95 435 }
Chris@95 436
Chris@95 437 if (highlighted) {
Chris@95 438 highPriority.push_back(file);
Chris@95 439 } else {
Chris@95 440 lowPriority.push_back(file);
Chris@93 441 }
Chris@93 442 }
Chris@95 443
Chris@95 444 foreach (QString file, highPriority) {
Chris@95 445 QListWidgetItem *item = new QListWidgetItem(file);
Chris@95 446 w->addItem(item);
Chris@118 447 item->setForeground(QColor("#d40000")); //!!! and a nice gold star
Chris@95 448 item->setSelected(selectedFiles.contains(file));
Chris@95 449 }
Chris@95 450
Chris@95 451 foreach (QString file, lowPriority) {
Chris@95 452 QListWidgetItem *item = new QListWidgetItem(file);
Chris@95 453 w->addItem(item);
Chris@95 454 item->setSelected(selectedFiles.contains(file));
Chris@95 455 }
Chris@95 456
Chris@100 457 setLabelFor(w, s, !highPriority.empty());
Chris@100 458
Chris@115 459 if (files.empty()) {
Chris@115 460 w->parentWidget()->hide();
Chris@115 461 } else {
Chris@115 462 w->parentWidget()->show();
Chris@115 463 haveAnything = true;
Chris@115 464 }
Chris@93 465 }
Chris@115 466
Chris@115 467 m_noModificationsLabel->setVisible(!haveAnything);
Chris@115 468
Chris@115 469 updateStateLabel();
Chris@88 470 }
Chris@88 471
Chris@100 472 void FileStatusWidget::setLabelFor(QWidget *w, FileStates::State s, bool addHighlight)
Chris@100 473 {
Chris@100 474 QString text = labelFor(s, addHighlight);
Chris@100 475 QWidget *p = w->parentWidget();
Chris@100 476 QList<QLabel *> ql = p->findChildren<QLabel *>();
Chris@100 477 if (!ql.empty()) ql[0]->setText(text);
Chris@100 478 }
Chris@115 479
Chris@115 480 void FileStatusWidget::updateStateLabel()
Chris@115 481 {
Chris@115 482 m_stateLabel->setText(m_state);
Chris@115 483 }