Commit Dialog:

- Commit messages must not be empty
- Double-clicking a file invokes diff
FileAction Dialog:
- Optional checkbox

MainWindow:
- Added Rename/Undo/About Actions
- Added Renamed/Missing file states
- Added tooltips to status column
- Support for running fossil in detached mode (to prevent killing fossil due to timeout on diff sessions)


FossilOrigin-Name: ea42e7cd8b7041d82e35720b14e30437b6db6e0d
This commit is contained in:
kostas
2011-08-05 15:25:38 +00:00
parent 54ca948fae
commit 433ef8252a
13 changed files with 281 additions and 475 deletions

View File

@ -10,15 +10,14 @@
#include <QTemporaryFile>
#include <QMessageBox>
#include <QUrl>
#include <QInputDialog>
#include "CommitDialog.h"
#include "FileActionDialog.h"
#define SILENT_STATUS true
#define COUNTOF(array) (sizeof(array)/sizeof(array[0]))
#define DEV_SETTINGS
//#define DEV_SETTINGS
enum
{
@ -88,6 +87,12 @@ MainWindow::~MainWindow()
#endif
delete ui;
}
const QString &MainWindow::getCurrentWorkspace()
{
Q_ASSERT(currentWorkspace<workspaces.size());
Q_ASSERT(QDir(workspaces[currentWorkspace]).exists());
return workspaces[currentWorkspace];
}
//------------------------------------------------------------------------------
void MainWindow::on_actionRefresh_triggered()
@ -104,6 +109,7 @@ void MainWindow::on_actionOpen_triggered()
workspaces.append(path);
currentWorkspace = workspaces.size()-1;
on_actionClearLog_triggered();
stopUI();
refresh();
}
}
@ -217,20 +223,41 @@ void MainWindow::scanWorkspace()
continue;
QString status_text = line.left(10).trimmed();
QString fname = line.right(line.length() - 10).trimmed();
FileEntry::EntryType type = FileEntry::TYPE_UNKNOWN;
bool add_missing = false;
if(status_text=="EDITED")
type = FileEntry::TYPE_EDITTED;
if(status_text=="ADDED")
else if(status_text=="ADDED")
type = FileEntry::TYPE_ADDED;
if(status_text=="DELETED")
else if(status_text=="DELETED")
{
type = FileEntry::TYPE_DELETED;
add_missing = true;
}
else if(status_text=="MISSING")
{
type = FileEntry::TYPE_MISSING;
add_missing = true;
}
else if(status_text=="RENAMED")
type = FileEntry::TYPE_RENAMED;
else if(status_text=="UNCHANGED")
type = FileEntry::TYPE_UNCHANGED;
QString fname = line.right(line.length() - 10).trimmed();
filemap_t::iterator it = workspaceFiles.find(fname);
if(add_missing && it==workspaceFiles.end())
{
FileEntry e;
QFileInfo info(wkdir+QDir::separator()+fname);
e.set(info, type, wkdir);
workspaceFiles.insert(e.getFilename(), e);
}
it = workspaceFiles.find(fname);
Q_ASSERT(it!=workspaceFiles.end());
it.value().setType(type);
@ -240,13 +267,14 @@ void MainWindow::scanWorkspace()
// Clear all rows (except header)
itemModel.removeRows(0, itemModel.rowCount());
struct { FileEntry::EntryType type; const char *tag; const char *icon; }
stats[]=
{
{ FileEntry::TYPE_EDITTED, "E", ":icons/icons/Button Blank Yellow-01.png" },
{ FileEntry::TYPE_UNCHANGED, "U", ":icons/icons/Button Blank Green-01.png" },
{ FileEntry::TYPE_ADDED, "A", ":icons/icons/Button Add-01.png" },
{ FileEntry::TYPE_DELETED, "D", ":icons/icons/Button Close-01.png" },
struct { FileEntry::EntryType type; const char *tag; const char *tooltip; const char *icon; }
stats[] = {
{ FileEntry::TYPE_EDITTED, "E", "Editted", ":icons/icons/Button Blank Yellow-01.png" },
{ FileEntry::TYPE_UNCHANGED, "U", "Unchanged", ":icons/icons/Button Blank Green-01.png" },
{ FileEntry::TYPE_ADDED, "A", "Added", ":icons/icons/Button Add-01.png" },
{ FileEntry::TYPE_DELETED, "D", "Deleted", ":icons/icons/Button Close-01.png" },
{ FileEntry::TYPE_RENAMED, "R", "Renamed", ":icons/icons/Button Reload-01.png" },
{ FileEntry::TYPE_MISSING, "M", "Missing", ":icons/icons/Button Help-01.png" },
};
size_t i=0;
@ -256,6 +284,7 @@ void MainWindow::scanWorkspace()
// Status Column
const char *tag = "?"; // Default Tag
const char *tooltip = "Unknown";
const char *icon = ":icons/icons/Button Blank Gray-01.png"; // Default icon
for(size_t t=0; t<COUNTOF(stats); ++t)
@ -263,12 +292,15 @@ void MainWindow::scanWorkspace()
if(e.getType() == stats[t].type)
{
tag = stats[t].tag;
tooltip = stats[t].tooltip;
icon = stats[t].icon;
break;
}
}
itemModel.setItem(i, COLUMN_STATUS, new QStandardItem(QIcon(icon), tag));
QStandardItem *status = new QStandardItem(QIcon(icon), tag);
status->setToolTip(tooltip);
itemModel.setItem(i, COLUMN_STATUS, status);
QString path = e.getFilename();
path = path.left(path.indexOf(e.getFileInfo().fileName()));
@ -347,10 +379,10 @@ void MainWindow::on_actionClearLog_triggered()
}
//------------------------------------------------------------------------------
bool MainWindow::runFossil(QStringList &result, const QStringList &args, bool silent)
bool MainWindow::runFossil(QStringList &result, const QStringList &args, bool silent, bool detached)
{
int exit_code = EXIT_FAILURE;
if(!runFossil(result, args, exit_code, silent))
if(!runFossil(result, args, exit_code, silent, detached))
return false;
return exit_code == EXIT_SUCCESS;
@ -358,17 +390,22 @@ bool MainWindow::runFossil(QStringList &result, const QStringList &args, bool si
//------------------------------------------------------------------------------
// Run fossil. Returns true if execution was succesfull regardless if fossil
// issued an error
bool MainWindow::runFossil(QStringList &result, const QStringList &args, int &exitCode, bool silent)
bool MainWindow::runFossil(QStringList &result, const QStringList &args, int &exitCode, bool silent, bool detached)
{
QProcess process(this);
process.setProcessChannelMode(QProcess::MergedChannels);
QString wkdir = getCurrentWorkspace();
process.setWorkingDirectory(wkdir);
if(!silent)
log("> fossil "+args.join(" ")+"\n");
QString wkdir = getCurrentWorkspace();
if(detached)
{
return QProcess::startDetached(fossilPath, args, wkdir);
}
QProcess process(this);
process.setProcessChannelMode(QProcess::MergedChannels);
process.setWorkingDirectory(wkdir);
process.start(fossilPath, args);
if(!process.waitForStarted())
{
@ -401,7 +438,8 @@ bool MainWindow::runFossil(QStringList &result, const QStringList &args, int &ex
//------------------------------------------------------------------------------
void MainWindow::addWorkspace(const QString &dir)
{
workspaces.append(dir);
QDir d(dir);
workspaces.append(d.absolutePath());
currentWorkspace = workspaces.size()-1;
}
//------------------------------------------------------------------------------
@ -503,18 +541,24 @@ void MainWindow::getSelectionFilenames(QStringList &filenames, int includeMask,
filenames.append(filename);
}
}
//------------------------------------------------------------------------------
bool MainWindow::diffFile(QString repoFile)
{
QStringList res;
int exitcode;
// Run the diff detached
return runFossil(res, QStringList() << "gdiff" << QuotePath(repoFile), exitcode, false, true);
}
//------------------------------------------------------------------------------
void MainWindow::on_actionDiff_triggered()
{
QStringList selection;
getSelectionFilenames(selection);
getSelectionFilenames(selection, FileEntry::TYPE_REPO);
for(QStringList::iterator it = selection.begin(); it!=selection.end(); ++it)
{
QStringList res;
if(!runFossil(res, QStringList() << "gdiff" << QuotePath(*it)))
if(!diffFile(*it))
return;
}
}
//------------------------------------------------------------------------------
@ -632,7 +676,7 @@ void MainWindow::on_actionCommit_triggered()
return;
QString msg;
if(!CommitDialog::run(msg, commitMessages, modified_files, this))
if(!CommitDialog::run(this, msg, commitMessages, modified_files))
return;
// Do commit
@ -661,7 +705,7 @@ void MainWindow::on_actionAdd_triggered()
if(selection.empty())
return;
if(!FileActionDialog::run(tr("Add files"), tr("The following files will be added. Are you sure?"), selection, this))
if(!FileActionDialog::run(this, tr("Add files"), tr("The following files will be added. Are you sure?"), selection))
return;
// Do Add
@ -680,15 +724,33 @@ void MainWindow::on_actionDelete_triggered()
QStringList unknown_files;
getSelectionFilenames(unknown_files, FileEntry::TYPE_UNKNOWN);
if(repo_files.empty() && unknown_files.empty())
QStringList all_files = repo_files+unknown_files;
if(all_files.empty())
return;
if(!FileActionDialog::run(tr("Delete files"), tr("The following files will be deleted. Are you sure?"), repo_files+unknown_files, this))
bool remove_local = false;
if(!FileActionDialog::run(this, tr("Remove files"), tr("The following files will be removed from the repository.\nAre you sure?"), all_files, tr("Also delete the local files"), &remove_local ))
return;
// Do Delete
QStringList res;
runFossil(res, QStringList() << "delete" << QuotePaths(repo_files) );
if(!repo_files.empty())
{
// Do Delete
QStringList res;
if(!runFossil(res, QStringList() << "delete" << QuotePaths(repo_files)))
return;
}
if(remove_local)
{
for(int i=0; i<all_files.size(); ++i)
{
QFileInfo fi(getCurrentWorkspace() + QDir::separator() + all_files[i]);
Q_ASSERT(fi.exists());
QFile::remove(fi.filePath());
}
}
refresh();
}
@ -697,12 +759,12 @@ void MainWindow::on_actionDelete_triggered()
void MainWindow::on_actionRevert_triggered()
{
QStringList modified_files;
getSelectionFilenames(modified_files, FileEntry::TYPE_ADDED|FileEntry::TYPE_EDITTED);
getSelectionFilenames(modified_files, FileEntry::TYPE_EDITTED|FileEntry::TYPE_DELETED|FileEntry::TYPE_MISSING);
if(modified_files.empty())
return;
if(!FileActionDialog::run(tr("Revert files"), tr("The following files will be reverted. Are you sure?"), modified_files, this))
if(!FileActionDialog::run(this, tr("Revert files"), tr("The following files will be reverted. Are you sure?"), modified_files))
return;
// Do Revert
@ -712,6 +774,42 @@ void MainWindow::on_actionRevert_triggered()
refresh();
}
//------------------------------------------------------------------------------
void MainWindow::on_actionRename_triggered()
{
QStringList repo_files;
getSelectionFilenames(repo_files, FileEntry::TYPE_REPO);
if(repo_files.length()!=1)
return;
QFileInfo fi_before(repo_files[0]);
bool ok = false;
QString new_name = QInputDialog::getText(this, tr("Rename"), tr("Enter new name"), QLineEdit::Normal, fi_before.filePath(), &ok );
if(!ok)
return;
QFileInfo fi_after(new_name);
if(fi_after.exists())
{
QMessageBox::critical(this, tr("Error"), tr("File ")+new_name+tr(" already exists.\nRename aborted."), QMessageBox::Ok );
return;
}
// Do Rename
QStringList res;
runFossil(res, QStringList() << "mv" << QuotePath(fi_before.filePath()) << QuotePath(fi_after.filePath()) );
QString wkdir = getCurrentWorkspace() + QDir::separator();
// Also rename the file
QFile::rename( wkdir+fi_before.filePath(), wkdir+fi_after.filePath());
refresh();
}
//------------------------------------------------------------------------------
void MainWindow::on_actionNew_triggered()
{
@ -732,6 +830,7 @@ void MainWindow::on_actionNew_triggered()
QMessageBox::critical(this, tr("Error"), tr("A repository file already exists.\nRepository creation aborted."), QMessageBox::Ok );
return;
}
stopUI();
QFileInfo path_info(path);
Q_ASSERT(path_info.dir().exists());
@ -753,17 +852,15 @@ void MainWindow::on_actionNew_triggered()
QMessageBox::critical(this, tr("Error"), tr("Repository checkout failed."), QMessageBox::Ok );
return;
}
refresh();
}
//------------------------------------------------------------------------------
void MainWindow::on_actionClone_triggered()
{
stopUI();
}
//------------------------------------------------------------------------------
void MainWindow::on_actionOpenContaining_triggered()
{
@ -783,3 +880,36 @@ void MainWindow::on_actionOpenContaining_triggered()
QUrl url = QUrl::fromLocalFile(target);
QDesktopServices::openUrl(url);
}
//------------------------------------------------------------------------------
void MainWindow::on_actionUndo_triggered()
{
// Gather Undo actions
QStringList res;
if(!runFossil(res, QStringList() << "undo" << "--explain" ))
return;
if(res.length()>0 && res[0]=="No undo or redo is available")
return;
if(!FileActionDialog::run(this, tr("Undo"), tr("The following actions will be undone. Are you sure?"), res))
return;
// Do Undo
runFossil(res, QStringList() << "undo" );
refresh();
}
//------------------------------------------------------------------------------
void MainWindow::on_actionAbout_triggered()
{
QMessageBox::about(this, tr("About..."), tr(
"Fuel, a GUI frontend to Fossil SCM\n"
"by Kostas Karanikolas\n"
"Released under the GNU GPL\n\n"
"Icon-set by Deleket - Jojo Mendoza\n"
"Available under the CC Attribution-Noncommercial-No Derivate 3.0 License"));
}