annotate filestatuswidget.cpp @ 186:6c15700f4103

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