Mercurial > hg > easyhg
comparison 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 |
comparison
equal
deleted
inserted
replaced
369:19cce6d2c470 | 370:b9c153e00e84 |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 EasyMercurial | |
5 | |
6 Based on HgExplorer by Jari Korhonen | |
7 Copyright (c) 2010 Jari Korhonen | |
8 Copyright (c) 2011 Chris Cannam | |
9 Copyright (c) 2011 Queen Mary, University of London | |
10 | |
11 This program is free software; you can redistribute it and/or | |
12 modify it under the terms of the GNU General Public License as | |
13 published by the Free Software Foundation; either version 2 of the | |
14 License, or (at your option) any later version. See the file | |
15 COPYING included with this distribution for more information. | |
16 */ | |
17 | |
18 #include "filestatuswidget.h" | |
19 #include "debug.h" | |
20 #include "multichoicedialog.h" | |
21 | |
22 #include <QLabel> | |
23 #include <QListWidget> | |
24 #include <QGridLayout> | |
25 #include <QFileInfo> | |
26 #include <QApplication> | |
27 #include <QDateTime> | |
28 #include <QPushButton> | |
29 #include <QToolButton> | |
30 #include <QDir> | |
31 #include <QProcess> | |
32 #include <QCheckBox> | |
33 #include <QSettings> | |
34 #include <QAction> | |
35 | |
36 FileStatusWidget::FileStatusWidget(QWidget *parent) : | |
37 QWidget(parent), | |
38 m_dateReference(0) | |
39 { | |
40 QGridLayout *layout = new QGridLayout; | |
41 layout->setMargin(10); | |
42 setLayout(layout); | |
43 | |
44 int row = 0; | |
45 | |
46 m_noModificationsLabel = new QLabel; | |
47 setNoModificationsLabelText(); | |
48 layout->addWidget(m_noModificationsLabel, row, 0); | |
49 m_noModificationsLabel->hide(); | |
50 | |
51 m_simpleLabels[FileStates::Clean] = tr("Unmodified:"); | |
52 m_simpleLabels[FileStates::Modified] = tr("Modified:"); | |
53 m_simpleLabels[FileStates::Added] = tr("Added:"); | |
54 m_simpleLabels[FileStates::Removed] = tr("Removed:"); | |
55 m_simpleLabels[FileStates::Missing] = tr("Missing:"); | |
56 m_simpleLabels[FileStates::InConflict] = tr("In Conflict:"); | |
57 m_simpleLabels[FileStates::Unknown] = tr("Untracked:"); | |
58 m_simpleLabels[FileStates::Ignored] = tr("Ignored:"); | |
59 | |
60 m_actionLabels[FileStates::Annotate] = tr("Show annotated version"); | |
61 m_actionLabels[FileStates::Diff] = tr("Diff to parent"); | |
62 m_actionLabels[FileStates::Commit] = tr("Commit..."); | |
63 m_actionLabels[FileStates::Revert] = tr("Revert to last committed state"); | |
64 m_actionLabels[FileStates::Rename] = tr("Rename..."); | |
65 m_actionLabels[FileStates::Copy] = tr("Copy..."); | |
66 m_actionLabels[FileStates::Add] = tr("Add to version control"); | |
67 m_actionLabels[FileStates::Remove] = tr("Remove from version control"); | |
68 m_actionLabels[FileStates::RedoMerge] = tr("Redo merge"); | |
69 m_actionLabels[FileStates::MarkResolved] = tr("Mark conflict as resolved"); | |
70 m_actionLabels[FileStates::Ignore] = tr("Ignore"); | |
71 m_actionLabels[FileStates::UnIgnore] = tr("Stop ignoring"); | |
72 | |
73 m_descriptions[FileStates::Clean] = tr("You have not changed these files."); | |
74 m_descriptions[FileStates::Modified] = tr("You have changed these files since you last committed them."); | |
75 m_descriptions[FileStates::Added] = tr("These files will be added to version control next time you commit them."); | |
76 m_descriptions[FileStates::Removed] = tr("These files will be removed from version control next time you commit them.<br>" | |
77 "They will not be deleted from the local folder."); | |
78 m_descriptions[FileStates::Missing] = tr("These files are recorded in the version control, but absent from your working folder.<br>" | |
79 "If you intended to delete them, select them and use Remove to tell the version control system about it.<br>" | |
80 "If you deleted them by accident, select them and use Revert to restore their previous contents."); | |
81 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."); | |
82 m_descriptions[FileStates::Unknown] = tr("These files are in your working folder but are not under version control.<br>" | |
83 // "Select a file and use Add to place it under version control or Ignore to remove it from this list."); | |
84 "Select a file and use Add to place it under version control."); | |
85 m_descriptions[FileStates::Ignored] = tr("These files have names that match entries in the working folder's .hgignore file,<br>" | |
86 "and so will be ignored by the version control system."); | |
87 | |
88 m_highlightExplanation = tr("Files highlighted <font color=#d40000>in red</font> " | |
89 "have appeared since your most recent commit or update."); | |
90 | |
91 m_boxesParent = new QWidget(this); | |
92 layout->addWidget(m_boxesParent, ++row, 0); | |
93 | |
94 QGridLayout *boxesLayout = new QGridLayout; | |
95 boxesLayout->setMargin(0); | |
96 m_boxesParent->setLayout(boxesLayout); | |
97 int boxRow = 0; | |
98 | |
99 for (int i = int(FileStates::FirstState); | |
100 i <= int(FileStates::LastState); ++i) { | |
101 | |
102 FileStates::State s = FileStates::State(i); | |
103 | |
104 QWidget *box = new QWidget(m_boxesParent); | |
105 QGridLayout *boxlayout = new QGridLayout; | |
106 boxlayout->setMargin(0); | |
107 box->setLayout(boxlayout); | |
108 | |
109 boxlayout->addItem(new QSpacerItem(3, 3), 0, 0); | |
110 | |
111 QLabel *label = new QLabel(labelFor(s)); | |
112 label->setWordWrap(true); | |
113 boxlayout->addWidget(label, 1, 0); | |
114 | |
115 QListWidget *w = new QListWidget; | |
116 m_stateListMap[s] = w; | |
117 w->setSelectionMode(QListWidget::ExtendedSelection); | |
118 boxlayout->addWidget(w, 2, 0); | |
119 | |
120 connect(w, SIGNAL(itemSelectionChanged()), | |
121 this, SLOT(itemSelectionChanged())); | |
122 connect(w, SIGNAL(itemDoubleClicked(QListWidgetItem *)), | |
123 this, SLOT(itemDoubleClicked(QListWidgetItem *))); | |
124 | |
125 FileStates::Activities activities = m_fileStates.activitiesSupportedBy(s); | |
126 int prevGroup = -1; | |
127 foreach (FileStates::Activity a, activities) { | |
128 // Skip activities which are not yet implemented | |
129 if (a == FileStates::Ignore || | |
130 a == FileStates::UnIgnore) { | |
131 continue; | |
132 } | |
133 int group = FileStates::activityGroup(a); | |
134 if (group != prevGroup && prevGroup != -1) { | |
135 QAction *sep = new QAction("", w); | |
136 sep->setSeparator(true); | |
137 w->insertAction(0, sep); | |
138 } | |
139 prevGroup = group; | |
140 QAction *act = new QAction(m_actionLabels[a], w); | |
141 act->setProperty("state", s); | |
142 act->setProperty("activity", a); | |
143 connect(act, SIGNAL(triggered()), this, SLOT(menuActionActivated())); | |
144 w->insertAction(0, act); | |
145 } | |
146 w->setContextMenuPolicy(Qt::ActionsContextMenu); | |
147 | |
148 boxlayout->addItem(new QSpacerItem(2, 2), 3, 0); | |
149 | |
150 boxesLayout->addWidget(box, ++boxRow, 0); | |
151 m_boxes.push_back(box); | |
152 box->hide(); | |
153 } | |
154 | |
155 m_gridlyLayout = false; | |
156 | |
157 layout->setRowStretch(++row, 20); | |
158 | |
159 layout->addItem(new QSpacerItem(8, 8), ++row, 0); | |
160 | |
161 m_showAllFiles = new QCheckBox(tr("Show all files"), this); | |
162 m_showAllFiles->setEnabled(false); | |
163 layout->addWidget(m_showAllFiles, ++row, 0, Qt::AlignLeft); | |
164 connect(m_showAllFiles, SIGNAL(toggled(bool)), | |
165 this, SIGNAL(showAllChanged(bool))); | |
166 } | |
167 | |
168 FileStatusWidget::~FileStatusWidget() | |
169 { | |
170 delete m_dateReference; | |
171 } | |
172 | |
173 QString FileStatusWidget::labelFor(FileStates::State s, bool addHighlightExplanation) | |
174 { | |
175 QSettings settings; | |
176 settings.beginGroup("Presentation"); | |
177 if (settings.value("showhelpfultext", true).toBool()) { | |
178 if (addHighlightExplanation) { | |
179 return QString("<qt><b>%1</b><br>%2<br>%3</qt>") | |
180 .arg(m_simpleLabels[s]) | |
181 .arg(m_descriptions[s]) | |
182 .arg(m_highlightExplanation); | |
183 } else { | |
184 return QString("<qt><b>%1</b><br>%2</qt>") | |
185 .arg(m_simpleLabels[s]) | |
186 .arg(m_descriptions[s]); | |
187 } | |
188 } else { | |
189 return QString("<qt><b>%1</b></qt>") | |
190 .arg(m_simpleLabels[s]); | |
191 } | |
192 settings.endGroup(); | |
193 } | |
194 | |
195 void FileStatusWidget::setNoModificationsLabelText() | |
196 { | |
197 QSettings settings; | |
198 settings.beginGroup("Presentation"); | |
199 if (settings.value("showhelpfultext", true).toBool()) { | |
200 m_noModificationsLabel->setText | |
201 (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>") | |
202 #if defined Q_OS_MAC | |
203 .arg(tr("To open the working folder in Finder,<br>click on the “Local” folder path shown above.")) | |
204 #elif defined Q_OS_WIN32 | |
205 .arg(tr("To open the working folder in Windows Explorer,<br>click on the “Local” folder path shown above.")) | |
206 #else | |
207 .arg(tr("To open the working folder in your system file manager,<br>click the “Local” folder path shown above.")) | |
208 #endif | |
209 ); | |
210 } else { | |
211 m_noModificationsLabel->setText | |
212 (tr("<qt>You have no uncommitted changes.</qt>")); | |
213 } | |
214 } | |
215 | |
216 | |
217 void FileStatusWidget::menuActionActivated() | |
218 { | |
219 QAction *act = qobject_cast<QAction *>(sender()); | |
220 if (!act) return; | |
221 | |
222 FileStates::State state = (FileStates::State) | |
223 act->property("state").toUInt(); | |
224 FileStates::Activity activity = (FileStates::Activity) | |
225 act->property("activity").toUInt(); | |
226 | |
227 DEBUG << "menuActionActivated: state = " << state << ", activity = " | |
228 << activity << endl; | |
229 | |
230 if (!FileStates::supportsActivity(state, activity)) { | |
231 std::cerr << "WARNING: FileStatusWidget::menuActionActivated: " | |
232 << "Action state " << state << " does not support activity " | |
233 << activity << std::endl; | |
234 return; | |
235 } | |
236 | |
237 QStringList files = getSelectedFilesInState(state); | |
238 | |
239 switch (activity) { | |
240 case FileStates::Annotate: emit annotateFiles(files); break; | |
241 case FileStates::Diff: emit diffFiles(files); break; | |
242 case FileStates::Commit: emit commitFiles(files); break; | |
243 case FileStates::Revert: emit revertFiles(files); break; | |
244 case FileStates::Rename: emit renameFiles(files); break; | |
245 case FileStates::Copy: emit copyFiles(files); break; | |
246 case FileStates::Add: emit addFiles(files); break; | |
247 case FileStates::Remove: emit removeFiles(files); break; | |
248 case FileStates::RedoMerge: emit redoFileMerges(files); break; | |
249 case FileStates::MarkResolved: emit markFilesResolved(files); break; | |
250 case FileStates::Ignore: emit ignoreFiles(files); break; | |
251 case FileStates::UnIgnore: emit unIgnoreFiles(files); break; | |
252 } | |
253 } | |
254 | |
255 void FileStatusWidget::itemDoubleClicked(QListWidgetItem *item) | |
256 { | |
257 QStringList files; | |
258 QString file = item->text(); | |
259 files << file; | |
260 | |
261 switch (m_fileStates.stateOf(file)) { | |
262 | |
263 case FileStates::Modified: | |
264 case FileStates::InConflict: | |
265 emit diffFiles(files); | |
266 break; | |
267 | |
268 case FileStates::Clean: | |
269 case FileStates::Missing: | |
270 emit annotateFiles(files); | |
271 break; | |
272 } | |
273 } | |
274 | |
275 void FileStatusWidget::itemSelectionChanged() | |
276 { | |
277 DEBUG << "FileStatusWidget::itemSelectionChanged" << endl; | |
278 | |
279 QListWidget *list = qobject_cast<QListWidget *>(sender()); | |
280 | |
281 if (list) { | |
282 foreach (QListWidget *w, m_stateListMap) { | |
283 if (w != list) { | |
284 w->blockSignals(true); | |
285 w->clearSelection(); | |
286 w->blockSignals(false); | |
287 } | |
288 } | |
289 } | |
290 | |
291 m_selectedFiles.clear(); | |
292 | |
293 foreach (QListWidget *w, m_stateListMap) { | |
294 QList<QListWidgetItem *> sel = w->selectedItems(); | |
295 foreach (QListWidgetItem *i, sel) { | |
296 m_selectedFiles.push_back(i->text()); | |
297 DEBUG << "file " << i->text() << " is selected" << endl; | |
298 } | |
299 } | |
300 | |
301 emit selectionChanged(); | |
302 } | |
303 | |
304 void FileStatusWidget::clearSelections() | |
305 { | |
306 m_selectedFiles.clear(); | |
307 foreach (QListWidget *w, m_stateListMap) { | |
308 w->clearSelection(); | |
309 } | |
310 } | |
311 | |
312 bool FileStatusWidget::haveChangesToCommit() const | |
313 { | |
314 return !getAllCommittableFiles().empty(); | |
315 } | |
316 | |
317 bool FileStatusWidget::haveSelection() const | |
318 { | |
319 return !m_selectedFiles.empty(); | |
320 } | |
321 | |
322 QStringList FileStatusWidget::getSelectedFilesInState(FileStates::State s) const | |
323 { | |
324 QStringList files; | |
325 foreach (QString f, m_selectedFiles) { | |
326 if (m_fileStates.stateOf(f) == s) files.push_back(f); | |
327 } | |
328 return files; | |
329 } | |
330 | |
331 QStringList FileStatusWidget::getSelectedFilesSupportingActivity(FileStates::Activity a) const | |
332 { | |
333 QStringList files; | |
334 foreach (QString f, m_selectedFiles) { | |
335 if (m_fileStates.supportsActivity(f, a)) files.push_back(f); | |
336 } | |
337 return files; | |
338 } | |
339 | |
340 QStringList FileStatusWidget::getAllCommittableFiles() const | |
341 { | |
342 return m_fileStates.filesSupportingActivity(FileStates::Commit); | |
343 } | |
344 | |
345 QStringList FileStatusWidget::getAllRevertableFiles() const | |
346 { | |
347 return m_fileStates.filesSupportingActivity(FileStates::Revert); | |
348 } | |
349 | |
350 QStringList FileStatusWidget::getAllUnresolvedFiles() const | |
351 { | |
352 return m_fileStates.filesInState(FileStates::InConflict); | |
353 } | |
354 | |
355 QStringList FileStatusWidget::getSelectedAddableFiles() const | |
356 { | |
357 return getSelectedFilesSupportingActivity(FileStates::Add); | |
358 } | |
359 | |
360 QStringList FileStatusWidget::getSelectedRemovableFiles() const | |
361 { | |
362 return getSelectedFilesSupportingActivity(FileStates::Remove); | |
363 } | |
364 | |
365 QString | |
366 FileStatusWidget::localPath() const | |
367 { | |
368 return m_localPath; | |
369 } | |
370 | |
371 void | |
372 FileStatusWidget::setLocalPath(QString p) | |
373 { | |
374 m_localPath = p; | |
375 delete m_dateReference; | |
376 m_dateReference = new QFileInfo(p + "/.hg/dirstate"); | |
377 if (!m_dateReference->exists() || | |
378 !m_dateReference->isFile() || | |
379 !m_dateReference->isReadable()) { | |
380 DEBUG << "FileStatusWidget::setLocalPath: date reference file " | |
381 << m_dateReference->absoluteFilePath() | |
382 << " does not exist, is not a file, or cannot be read" | |
383 << endl; | |
384 delete m_dateReference; | |
385 m_dateReference = 0; | |
386 m_showAllFiles->setEnabled(false); | |
387 } else { | |
388 m_showAllFiles->setEnabled(true); | |
389 } | |
390 } | |
391 | |
392 void | |
393 FileStatusWidget::setFileStates(FileStates p) | |
394 { | |
395 m_fileStates = p; | |
396 updateWidgets(); | |
397 } | |
398 | |
399 void | |
400 FileStatusWidget::updateWidgets() | |
401 { | |
402 QDateTime lastInteractionTime; | |
403 if (m_dateReference) { | |
404 lastInteractionTime = m_dateReference->lastModified(); | |
405 DEBUG << "reference time: " << lastInteractionTime << endl; | |
406 } | |
407 | |
408 QSet<QString> selectedFiles; | |
409 foreach (QString f, m_selectedFiles) selectedFiles.insert(f); | |
410 | |
411 int visibleCount = 0; | |
412 | |
413 foreach (FileStates::State s, m_stateListMap.keys()) { | |
414 | |
415 QListWidget *w = m_stateListMap[s]; | |
416 w->clear(); | |
417 QStringList files = m_fileStates.filesInState(s); | |
418 | |
419 QStringList highPriority, lowPriority; | |
420 | |
421 foreach (QString file, files) { | |
422 | |
423 bool highlighted = false; | |
424 | |
425 if (s == FileStates::Unknown) { | |
426 // We want to highlight untracked files that have appeared | |
427 // since the last interaction with the repo | |
428 QString fn(m_localPath + "/" + file); | |
429 DEBUG << "comparing with " << fn << endl; | |
430 QFileInfo fi(fn); | |
431 if (fi.exists() && fi.created() > lastInteractionTime) { | |
432 DEBUG << "file " << fn << " is newer (" << fi.lastModified() | |
433 << ") than reference" << endl; | |
434 highlighted = true; | |
435 } | |
436 } | |
437 | |
438 if (highlighted) { | |
439 highPriority.push_back(file); | |
440 } else { | |
441 lowPriority.push_back(file); | |
442 } | |
443 } | |
444 | |
445 foreach (QString file, highPriority) { | |
446 QListWidgetItem *item = new QListWidgetItem(file); | |
447 w->addItem(item); | |
448 item->setForeground(QColor("#d40000")); | |
449 item->setSelected(selectedFiles.contains(file)); | |
450 } | |
451 | |
452 foreach (QString file, lowPriority) { | |
453 QListWidgetItem *item = new QListWidgetItem(file); | |
454 w->addItem(item); | |
455 item->setSelected(selectedFiles.contains(file)); | |
456 } | |
457 | |
458 setLabelFor(w, s, !highPriority.empty()); | |
459 | |
460 if (files.empty()) { | |
461 w->parentWidget()->hide(); | |
462 } else { | |
463 w->parentWidget()->show(); | |
464 ++visibleCount; | |
465 } | |
466 } | |
467 | |
468 m_noModificationsLabel->setVisible(visibleCount == 0); | |
469 | |
470 if (visibleCount > 3) { | |
471 layoutBoxesGridly(visibleCount); | |
472 } else { | |
473 layoutBoxesLinearly(); | |
474 } | |
475 | |
476 setNoModificationsLabelText(); | |
477 } | |
478 | |
479 void FileStatusWidget::layoutBoxesGridly(int visibleCount) | |
480 { | |
481 if (m_gridlyLayout && m_lastGridlyCount == visibleCount) return; | |
482 | |
483 delete m_boxesParent->layout(); | |
484 | |
485 QGridLayout *layout = new QGridLayout; | |
486 layout->setMargin(0); | |
487 m_boxesParent->setLayout(layout); | |
488 | |
489 int row = 0; | |
490 int col = 0; | |
491 | |
492 DEBUG << "FileStatusWidget::layoutBoxesGridly: visibleCount = " | |
493 << visibleCount << endl; | |
494 | |
495 for (int i = 0; i < m_boxes.size(); ++i) { | |
496 | |
497 if (!m_boxes[i]->isVisible()) continue; | |
498 | |
499 if (col == 0 && row >= (visibleCount+1)/2) { | |
500 layout->addItem(new QSpacerItem(10, 5), 0, 1); | |
501 col = 2; | |
502 row = 0; | |
503 } | |
504 | |
505 layout->addWidget(m_boxes[i], row, col); | |
506 | |
507 ++row; | |
508 } | |
509 | |
510 m_gridlyLayout = true; | |
511 m_lastGridlyCount = visibleCount; | |
512 } | |
513 | |
514 void FileStatusWidget::layoutBoxesLinearly() | |
515 { | |
516 if (!m_gridlyLayout) return; | |
517 | |
518 delete m_boxesParent->layout(); | |
519 | |
520 QGridLayout *layout = new QGridLayout; | |
521 layout->setMargin(0); | |
522 m_boxesParent->setLayout(layout); | |
523 | |
524 for (int i = 0; i < m_boxes.size(); ++i) { | |
525 layout->addWidget(m_boxes[i], i, 0); | |
526 } | |
527 | |
528 m_gridlyLayout = false; | |
529 } | |
530 | |
531 void FileStatusWidget::setLabelFor(QWidget *w, FileStates::State s, bool addHighlight) | |
532 { | |
533 QString text = labelFor(s, addHighlight); | |
534 QWidget *p = w->parentWidget(); | |
535 QList<QLabel *> ql = p->findChildren<QLabel *>(); | |
536 if (!ql.empty()) ql[0]->setText(text); | |
537 } | |
538 |