Index: fuel.pro ================================================================== --- fuel.pro +++ fuel.pro @@ -1,91 +1,102 @@ -#------------------------------------------------- -# Fuel -#------------------------------------------------- - -QT += core gui webkit - -contains(QT_VERSION, ^5\\..*) { - QT += widgets webkitwidgets -} - -TARGET = Fuel -TEMPLATE = app - -win32 { - RC_FILE = rsrc/fuel.rc - LIBS += -luser32 -lshell32 -luuid -} - -macx { - ICON = rsrc/icons/fuel.icns -} - -unix:!macx { - TARGET = fuel - ICON = rsrc/icons/fuel.png - PREFIX = /usr - BINDIR = $$PREFIX/bin - DATADIR = $$PREFIX/share - target.path = $$BINDIR - - desktop.path = $$DATADIR/applications - desktop.files += rsrc/fuel.desktop - - icon.path = $$DATADIR/icons/hicolor/256x256/apps - icon.files += rsrc/icons/fuel.png - - INSTALLS += target desktop icon - system(intl/convert.sh) -} - - -INCLUDEPATH += src - -SOURCES += src/main.cpp\ - src/MainWindow.cpp \ - src/CommitDialog.cpp \ - src/FileActionDialog.cpp \ - src/SettingsDialog.cpp \ - src/Utils.cpp \ - src/FileTableView.cpp \ - src/CloneDialog.cpp \ - src/LoggedProcess.cpp \ - src/BrowserWidget.cpp \ - src/CustomWebView.cpp \ - src/Fossil.cpp \ - src/Workspace.cpp - -HEADERS += src/MainWindow.h \ - src/CommitDialog.h \ - src/FileActionDialog.h \ - src/SettingsDialog.h \ - src/Utils.h \ - src/FileTableView.h \ - src/CloneDialog.h \ - src/LoggedProcess.h \ - src/BrowserWidget.h \ - src/CustomWebView.h \ - src/Fossil.h \ - src/Workspace.h - -FORMS += ui/MainWindow.ui \ - ui/CommitDialog.ui \ - ui/FileActionDialog.ui \ - ui/SettingsDialog.ui \ - ui/CloneDialog.ui \ - ui/BrowserWidget.ui - -RESOURCES += \ - rsrc/resources.qrc - -CODECFORTR = UTF-8 - -TRANSLATIONS += \ - intl/en_US.ts \ - intl/el_GR.ts \ - intl/de_DE.ts \ - intl/es_ES.ts \ - intl/fr_FR.ts \ - intl/ru_RU.ts \ - intl/pt_PT.ts - +#------------------------------------------------- +# Fuel +#------------------------------------------------- + +QT = core gui webkit + +contains(QT_VERSION, ^5\\..*) { + QT += widgets webkitwidgets + QT -= quick multimediawidgets opengl printsupport qml multimedia positioning sensors +} + +TARGET = Fuel +TEMPLATE = app + +win32 { + RC_FILE = rsrc/fuel.rc + LIBS += -luser32 -lshell32 -luuid +} + +macx { + ICON = rsrc/icons/fuel.icns +} + +unix:!macx { + TARGET = fuel + ICON = rsrc/icons/fuel.png + PREFIX = /usr + BINDIR = $$PREFIX/bin + DATADIR = $$PREFIX/share + target.path = $$BINDIR + + desktop.path = $$DATADIR/applications + desktop.files += rsrc/fuel.desktop + + icon.path = $$DATADIR/icons/hicolor/256x256/apps + icon.files += rsrc/icons/fuel.png + + INSTALLS += target desktop icon + system(intl/convert.sh) +} + + +INCLUDEPATH += src + +SOURCES += src/main.cpp\ + src/MainWindow.cpp \ + src/CommitDialog.cpp \ + src/FileActionDialog.cpp \ + src/SettingsDialog.cpp \ + src/FslSettingsDialog.cpp \ + src/CloneDialog.cpp \ + src/RevisionDialog.cpp \ + src/Utils.cpp \ + src/FileTableView.cpp \ + src/LoggedProcess.cpp \ + src/BrowserWidget.cpp \ + src/CustomWebView.cpp \ + src/Fossil.cpp \ + src/Workspace.cpp \ + src/SearchBox.cpp \ + src/Settings.cpp + +HEADERS += src/MainWindow.h \ + src/CommitDialog.h \ + src/FileActionDialog.h \ + src/SettingsDialog.h \ + src/FslSettingsDialog.h \ + src/CloneDialog.h \ + src/RevisionDialog.h \ + src/Utils.h \ + src/FileTableView.h \ + src/LoggedProcess.h \ + src/BrowserWidget.h \ + src/CustomWebView.h \ + src/Fossil.h \ + src/Workspace.h \ + src/SearchBox.h \ + src/Settings.h + +FORMS += ui/MainWindow.ui \ + ui/CommitDialog.ui \ + ui/FileActionDialog.ui \ + ui/SettingsDialog.ui \ + ui/FslSettingsDialog.ui \ + ui/CloneDialog.ui \ + ui/BrowserWidget.ui \ + ui/RevisionDialog.ui + +RESOURCES += \ + rsrc/resources.qrc + +CODECFORTR = UTF-8 + +TRANSLATIONS += \ + intl/en_US.ts \ + intl/el_GR.ts \ + intl/de_DE.ts \ + intl/es_ES.ts \ + intl/fr_FR.ts \ + intl/ru_RU.ts \ + intl/pt_PT.ts + Index: rsrc/resources.qrc ================================================================== --- rsrc/resources.qrc +++ rsrc/resources.qrc @@ -1,163 +1,167 @@ - - icons/Address Book-01.png - icons/Adobe Illustrator CS3 Document-01.png - icons/Adobe PDF Document-01.png - icons/Adobe Photoshop CS3 Document-01.png - icons/Battery-01.png - icons/Binoculars-01.png - icons/Book-01.png - icons/Briefcase-01.png - icons/Button Add-01.png - icons/Button Blank Blue-01.png - icons/Button Blank Gray-01.png - icons/Button Blank Green-01.png - icons/Button Blank Red-01.png - icons/Button Blank Yellow-01.png - icons/Button Cancel-01.png - icons/Button Close-01.png - icons/Button Delete-01.png - icons/Button Download-01.png - icons/Button Favorite-01.png - icons/Button Forward-01.png - icons/Button Help-01.png - icons/Button Info-01.png - icons/Button Log Off-01.png - icons/Button Next-01.png - icons/Button Pause-01.png - icons/Button Play-01.png - icons/Button Previous-01.png - icons/Button Refresh-01.png - icons/Button Reload-01.png - icons/Button Reminder-01.png - icons/Button Rewind-01.png - icons/Button Talk Balloon-01.png - icons/Button Turn Off-01.png - icons/Button Turn On-01.png - icons/Button Upload-01.png - icons/Button Warning-01.png - icons/Calculator-01.png - icons/Calendar Blue-01.png - icons/Calendar Green-01.png - icons/Calendar Red-01.png - icons/Clipboard-01.png - icons/Clipboard Paste-01.png - icons/Clock-01.png - icons/Coin-01.png - icons/Compressed File RAR-01.png - icons/Compressed File SIT-01.png - icons/Compressed File Zip-01.png - icons/Computer Monitor-01.png - icons/Computer Network-01.png - icons/Document-01.png - icons/Document Attach-01.png - icons/Document Blank-01.png - icons/Document Chart-01.png - icons/Document Copy-01.png - icons/Document Flow Chart-01.png - icons/Document Gant Chart-01.png - icons/Document Help-01.png - icons/Document Line Chart-01.png - icons/Document Microsoft Excel-01.png - icons/Document Microsoft PowerPoint-01.png - icons/Document Microsoft Word-01.png - icons/Document Organization Chart-01.png - icons/Document Preview-01.png - icons/Document-Revert-icon.png - icons/Document Text-01.png - icons/Edit Document-01.png - icons/Email-01.png - icons/Email Attachment-01.png - icons/Email Delete-01.png - icons/Email Download-01.png - icons/Email Forward-01.png - icons/Email Inbox-01.png - icons/Email Reply-01.png - icons/File Audio-01.png - icons/File Audio AIFF-01.png - icons/File Audio MP3-01.png - icons/File Audio WAV-01.png - icons/File Audio WMA-01.png - icons/File Delete-01.png - icons/File History-01.png - icons/File New-01.png - icons/File Open-01.png - icons/File Video 3GP-01.png - icons/File Video-01.png - icons/File Video AVI-01.png - icons/File Video MOV-01.png - icons/File Video MPEG-01.png - icons/File Video WMV-01.png - icons/Folder-01.png - icons/Folder Add-01.png - icons/Folder Compressed-01.png - icons/Folder Delete-01.png - icons/Folder Explorer-01.png - icons/Folder Generic Blue-01.png - icons/Folder Generic Green-01.png - icons/Folder Generic Red-01.png - icons/Folder Generic Silver-01.png - icons/Folder Open-01.png - icons/Folder RAR-01.png - icons/Games-01.png - icons/Gear-01.png - icons/Highlighter Blue-01.png - icons/Highlighter Green-01.png - icons/Highlighter Yellow-01.png - icons/Image BMP-01.png - icons/Image GIF-01.png - icons/Image JPEG-01.png - icons/Image PNG-01.png - icons/Image TIFF-01.png - icons/Lock Lock-01.png - icons/Lock Unlock-01.png - icons/My Documents-01.png - icons/My Ebooks-01.png - icons/My Music-01.png - icons/My Pictures.png - icons/My Videos-01.png - icons/My Websites-01.png - icons/Network Firewall-01.png - icons/Network MAC-01.png - icons/Network PC-01.png - icons/Network Refresh-01.png - icons/Pen Blue-01.png - icons/Pen Green-01.png - icons/Pen Red-01.png - icons/Save-01.png - icons/Text Edit.png - icons/USB-01.png - icons/User Administrator Blue-01.png - icons/User Administrator Green-01.png - icons/User Administrator Red-01.png - icons/User Chat-01.png - icons/User Clients-01.png - icons/User Coat Blue-01.png - icons/User Coat Green-01.png - icons/User Coat Red-01.png - icons/User Executive Blue-01.png - icons/User Executive Green-01.png - icons/User Executive Red-01.png - icons/User Group-01.png - icons/User Preppy Blue-01.png - icons/User Preppy Green-01.png - icons/User Preppy Red-01.png - icons/Web HTML-01.png - icons/Web XML-01.png - icons/Window-01.png - icons/Window Refresh-01.png - icons/Windows-01.png - icons/Windows Cascade-01.png - icons/Zoom-01.png - icons/Zoom In-01.png - icons/Zoom Out-01.png - - - intl/el_GR.qm - intl/de_DE.qm - intl/es_ES.qm - intl/fr_FR.qm - intl/ru_RU.qm - intl/pt_PT.qm - + + icons/Address Book-01.png + icons/Adobe Illustrator CS3 Document-01.png + icons/Adobe PDF Document-01.png + icons/Adobe Photoshop CS3 Document-01.png + icons/Battery-01.png + icons/Binoculars-01.png + icons/Book-01.png + icons/Briefcase-01.png + icons/Button Add-01.png + icons/Button Blank Blue-01.png + icons/Button Blank Gray-01.png + icons/Button Blank Green-01.png + icons/Button Blank Red-01.png + icons/Button Blank Yellow-01.png + icons/Button Cancel-01.png + icons/Button Close-01.png + icons/Button Close-01.png + icons/Button Close-01.png + icons/Button Delete-01.png + icons/Button Download-01.png + icons/Button Favorite-01.png + icons/Button Forward-01.png + icons/Button Help-01.png + icons/Button Info-01.png + icons/Button Log Off-01.png + icons/Button Next-01.png + icons/Button Pause-01.png + icons/Button Play-01.png + icons/Button Previous-01.png + icons/Button Refresh-01.png + icons/Button Reload-01.png + icons/Button Reload-01.png + icons/Button Reminder-01.png + icons/Button Rewind-01.png + icons/Button Talk Balloon-01.png + icons/Button Turn Off-01.png + icons/Button Turn On-01.png + icons/Button Upload-01.png + icons/Button Warning-01.png + icons/Calculator-01.png + icons/Calendar Blue-01.png + icons/Calendar Green-01.png + icons/Calendar Red-01.png + icons/Clipboard-01.png + icons/Clipboard Paste-01.png + icons/Clock-01.png + icons/Coin-01.png + icons/Compressed File RAR-01.png + icons/Compressed File SIT-01.png + icons/Compressed File Zip-01.png + icons/Computer Monitor-01.png + icons/Computer Network-01.png + icons/Document-01.png + icons/Document Attach-01.png + icons/Document Blank-01.png + icons/Document Chart-01.png + icons/Document Copy-01.png + icons/Document Flow Chart-01.png + icons/Document Gant Chart-01.png + icons/Document Help-01.png + icons/Document Line Chart-01.png + icons/Document Microsoft Excel-01.png + icons/Document Microsoft PowerPoint-01.png + icons/Document Microsoft Word-01.png + icons/Document Organization Chart-01.png + icons/Document Preview-01.png + icons/Document-Revert-icon.png + icons/Document Text-01.png + icons/Edit Document-01.png + icons/Email-01.png + icons/Email Attachment-01.png + icons/Email Delete-01.png + icons/Email Download-01.png + icons/Email Forward-01.png + icons/Email Inbox-01.png + icons/Email Reply-01.png + icons/File Audio-01.png + icons/File Audio AIFF-01.png + icons/File Audio MP3-01.png + icons/File Audio WAV-01.png + icons/File Audio WMA-01.png + icons/File Delete-01.png + icons/File History-01.png + icons/File New-01.png + icons/File Open-01.png + icons/File Video 3GP-01.png + icons/File Video-01.png + icons/File Video AVI-01.png + icons/File Video MOV-01.png + icons/File Video MPEG-01.png + icons/File Video WMV-01.png + icons/Folder-01.png + icons/Folder Add-01.png + icons/Folder Compressed-01.png + icons/Folder Delete-01.png + icons/Folder Explorer-01.png + icons/Folder Explorer-01.png + icons/Folder Generic Blue-01.png + icons/Folder Generic Green-01.png + icons/Folder Generic Red-01.png + icons/Folder Generic Silver-01.png + icons/Folder Open-01.png + icons/Folder RAR-01.png + icons/Games-01.png + icons/Gear-01.png + icons/Highlighter Blue-01.png + icons/Highlighter Green-01.png + icons/Highlighter Yellow-01.png + icons/Image BMP-01.png + icons/Image GIF-01.png + icons/Image JPEG-01.png + icons/Image PNG-01.png + icons/Image TIFF-01.png + icons/Lock Lock-01.png + icons/Lock Unlock-01.png + icons/My Documents-01.png + icons/My Ebooks-01.png + icons/My Music-01.png + icons/My Pictures.png + icons/My Videos-01.png + icons/My Websites-01.png + icons/Network Firewall-01.png + icons/Network MAC-01.png + icons/Network PC-01.png + icons/Network Refresh-01.png + icons/Pen Blue-01.png + icons/Pen Green-01.png + icons/Pen Red-01.png + icons/Save-01.png + icons/Text Edit.png + icons/USB-01.png + icons/User Administrator Blue-01.png + icons/User Administrator Green-01.png + icons/User Administrator Red-01.png + icons/User Chat-01.png + icons/User Clients-01.png + icons/User Coat Blue-01.png + icons/User Coat Green-01.png + icons/User Coat Red-01.png + icons/User Executive Blue-01.png + icons/User Executive Green-01.png + icons/User Executive Red-01.png + icons/User Group-01.png + icons/User Preppy Blue-01.png + icons/User Preppy Green-01.png + icons/User Preppy Red-01.png + icons/Web HTML-01.png + icons/Web XML-01.png + icons/Window-01.png + icons/Window Refresh-01.png + icons/Windows-01.png + icons/Windows Cascade-01.png + icons/Zoom-01.png + icons/Zoom In-01.png + icons/Zoom Out-01.png + + + intl/el_GR.qm + intl/de_DE.qm + intl/es_ES.qm + intl/fr_FR.qm + intl/ru_RU.qm + intl/pt_PT.qm + Index: src/CommitDialog.cpp ================================================================== --- src/CommitDialog.cpp +++ src/CommitDialog.cpp @@ -2,11 +2,11 @@ #include #include #include "ui_CommitDialog.h" #include "MainWindow.h" // Ugly. I know. -CommitDialog::CommitDialog(QWidget *parent, QString title, QStringList &files, const QStringList *history, bool singleLineEntry, const QString *checkBoxText, bool *checkBoxValue) : +CommitDialog::CommitDialog(QWidget *parent, const QString &title, QStringList &files, const QStringList *history, bool stashMode) : QDialog(parent, Qt::Sheet), ui(new Ui::CommitDialog) { ui->setupUi(this); ui->plainTextEdit->clear(); @@ -13,20 +13,17 @@ ui->listView->setModel(&itemModel); setWindowTitle(title); // Activate the appropriate control based on mode - ui->plainTextEdit->setVisible(!singleLineEntry); - ui->lineEdit->setVisible(singleLineEntry); + ui->plainTextEdit->setVisible(!stashMode); + ui->lineEdit->setVisible(stashMode); // Activate the checkbox if we have some text - ui->checkBox->setVisible(checkBoxText!=0); - if(checkBoxText && checkBoxValue) - { - ui->checkBox->setText(*checkBoxText); - ui->checkBox->setCheckState(*checkBoxValue ? Qt::Checked : Qt::Unchecked); - } + ui->chkRevertFiles->setVisible(stashMode); + + ui->widgetBranchOptions->setVisible(!stashMode); // Activate the combo if we have history ui->comboBox->setVisible(history!=0); if(history) { @@ -75,20 +72,16 @@ { delete ui; } //------------------------------------------------------------------------------ -bool CommitDialog::run(QWidget *parent, QString title, QStringList &files, QString &commitMsg, const QStringList *history, bool singleLineEntry, const QString *checkBoxText, bool *checkBoxValue) +bool CommitDialog::runCommit(QWidget* parent, QStringList& files, QString& commitMsg, const QStringList& commitMsgHistory, QString &branchName, bool &privateBranch) { - CommitDialog dlg(parent, title, files, history, singleLineEntry, checkBoxText, checkBoxValue); + CommitDialog dlg(parent, tr("Commit Changes"), files, &commitMsgHistory, false); int res = dlg.exec(); - if(singleLineEntry) - commitMsg = dlg.ui->lineEdit->text(); - else - commitMsg = dlg.ui->plainTextEdit->toPlainText(); - + commitMsg = dlg.ui->plainTextEdit->toPlainText(); if(res!=QDialog::Accepted) return false; files.clear(); @@ -98,16 +91,41 @@ if(si->checkState()!=Qt::Checked) continue; files.append(si->text()); } - if(checkBoxText) + branchName.clear(); + if(dlg.ui->chkNewBranch->isChecked()) + { + branchName = dlg.ui->lineBranchName->text().trimmed(); + privateBranch = dlg.ui->chkPrivateBranch->isChecked(); + } + + return true; +} + +//------------------------------------------------------------------------------ +bool CommitDialog::runStashNew(QWidget* parent, QStringList& stashedFiles, QString& stashName, bool& revertFiles) +{ + CommitDialog dlg(parent, tr("Stash Changes"), stashedFiles, NULL, true); + int res = dlg.exec(); + + stashName = dlg.ui->lineEdit->text(); + + if(res!=QDialog::Accepted) + return false; + + stashedFiles.clear(); + for(int i=0; icheckBox->checkState() == Qt::Checked; + QStandardItem *si = dlg.itemModel.item(i); + if(si->checkState()!=Qt::Checked) + continue; + stashedFiles.append(si->text()); } + revertFiles = dlg.ui->chkRevertFiles->checkState() == Qt::Checked; return true; } //------------------------------------------------------------------------------ void CommitDialog::on_comboBox_activated(int index) @@ -140,5 +158,12 @@ ++num_selected; } ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(num_selected>0); } + +//------------------------------------------------------------------------------ +void CommitDialog::on_chkNewBranch_toggled(bool checked) +{ + ui->chkPrivateBranch->setEnabled(checked); + ui->lineBranchName->setEnabled(checked); +} Index: src/CommitDialog.h ================================================================== --- src/CommitDialog.h +++ src/CommitDialog.h @@ -3,30 +3,32 @@ #include #include namespace Ui { - class CommitDialog; + class CommitDialog; } class CommitDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit CommitDialog(QWidget *parent, QString title, QStringList &files, const QStringList *history=0, bool singleLineEntry=false, const QString *checkBoxText=0, bool *checkBoxValue=0); - ~CommitDialog(); + explicit CommitDialog(QWidget *parent, const QString &title, QStringList &files, const QStringList *history, bool stashMode); + ~CommitDialog(); - static bool run(QWidget *parent, QString title, QStringList &files, QString &commitMsg, const QStringList *history=0, bool singleLineEntry=false, const QString *checkBoxText=0, bool *checkBoxValue=0); + static bool runStashNew(QWidget* parent, QStringList& stashedFiles, QString& stashName, bool &revertFiles); + static bool runCommit(QWidget* parent, QStringList& files, QString& commitMsg, const QStringList &commitMsgHistory, QString& branchName, bool& privateBranch); private slots: void on_comboBox_activated(int index); void on_listView_doubleClicked(const QModelIndex &index); void on_listView_clicked(const QModelIndex &index); + void on_chkNewBranch_toggled(bool checked); private: - Ui::CommitDialog *ui; + Ui::CommitDialog *ui; QStandardItemModel itemModel; QStringList commitMessages; }; #endif // COMMITDIALOG_H Index: src/Fossil.cpp ================================================================== --- src/Fossil.cpp +++ src/Fossil.cpp @@ -1,9 +1,8 @@ #include "Fossil.h" #include #include -#include #include #include #include #include #include @@ -12,10 +11,13 @@ static const unsigned char UTF8_BOM[] = { 0xEF, 0xBB, 0xBF }; // 19: [5c46757d4b9765] on 2012-04-22 04:41:15 static const QRegExp REGEX_STASH("\\s*(\\d+):\\s+\\[(.*)\\] on (\\d+)-(\\d+)-(\\d+) (\\d+):(\\d+):(\\d+)", Qt::CaseInsensitive); +// Listening for HTTP requests on TCP port 8081 +static const QRegExp REGEX_PORT(".*TCP port ([0-9]+)\\n", Qt::CaseSensitive); + /////////////////////////////////////////////////////////////////////////////// RepoStatus Fossil::getRepoStatus() { QStringList res; int exit_code = EXIT_FAILURE; @@ -48,10 +50,25 @@ { if(key=="project-name") projectName = value; else if(key=="repository") repositoryFile = value; + else if(key=="checkout") + { + // f2121dad5e4565f55ed9ef882484dd5934af565f 2015-04-26 17:27:39 UTC + QStringList tokens = value.split(' ', QString::SkipEmptyParts); + Q_ASSERT(tokens.length()>0); + currentRevision = tokens[0].trimmed(); + } + else if(key=="tags") + { + currentTags.clear(); + QStringList tokens = value.split(',', QString::SkipEmptyParts); + foreach(const QString &tag, tokens) + currentTags.append(tag); + currentTags.sort(); + } } } return run_ok ? REPO_OK : REPO_NOT_FOUND; } @@ -101,10 +118,16 @@ //------------------------------------------------------------------------------ bool Fossil::listFiles(QStringList &files) { return runFossil(QStringList() << "ls" << "-l", &files, RUNFLAGS_SILENT_ALL); } + +//------------------------------------------------------------------------------ +bool Fossil::status(QStringList &result) +{ + return runFossil(QStringList() << "status", &result, RUNFLAGS_SILENT_ALL); +} //------------------------------------------------------------------------------ bool Fossil::pushRepository() { return runFossil(QStringList() << "push"); @@ -174,11 +197,11 @@ // Run the diff detached return runFossil(QStringList() << "gdiff" << QuotePath(repoFile), 0, RUNFLAGS_DETACHED); } //------------------------------------------------------------------------------ -bool Fossil::commitFiles(const QStringList& fileList, const QString& comment) +bool Fossil::commitFiles(const QStringList& fileList, const QString& comment, const QString &newBranchName, bool isPrivateBranch) { // Do commit QString comment_fname; { QTemporaryFile temp_file; @@ -200,10 +223,21 @@ comment_file.close(); // Generate fossil parameters. QStringList params; params << "commit" << "--message-file" << QuotePath(comment_fname); + + // Commit to new branch + if(!newBranchName.isEmpty()) + { + params << "--branch" << newBranchName.trimmed(); + + // Private branches are not synced with remotes + if(isPrivateBranch) + params << "--private"; + } + params << QuotePaths(fileList); runFossil(params); QFile::remove(comment_fname); return true; @@ -284,17 +318,23 @@ result.clear(); return runFossil(params, &result); } //------------------------------------------------------------------------------ -bool Fossil::updateRepository(QStringList &result, bool explainOnly) +bool Fossil::updateRepository(QStringList &result, const QString &revision, bool explainOnly) { QStringList params; params << "update"; if(explainOnly) params << "--nochange"; + + if(revision.isEmpty()) + params << "--latest"; + else + params << revision; + result.clear(); return runFossil(params, &result); } @@ -414,10 +454,143 @@ //------------------------------------------------------------------------------ bool Fossil::stashDiff(const QString& name) { return runFossil(QStringList() << "stash" << "diff" << name, 0); } + +//------------------------------------------------------------------------------ +bool Fossil::tagList(QStringMap& tags) +{ + tags.clear(); + QStringList tagnames; + + if(!runFossil(QStringList() << "tag" << "ls", &tagnames, RUNFLAGS_SILENT_ALL)) + return false; + + QStringList info; + foreach(const QString &line, tagnames) + { + QString tag = line.trimmed(); + + if(tag.isEmpty()) + continue; + + info.clear(); + + if(!runFossil(QStringList() << "info" << "tag:"+tag, &info, RUNFLAGS_SILENT_ALL)) + return false; + + QStringMap props; + ParseProperties(props, info, ':'); + Q_ASSERT(props.contains("uuid")); + + // uuid: 0e29a46f036d2e0cc89727190ad34c2dfdc5737c 2015-04-27 15:41:45 UTC + QStringList uuid = props["uuid"].trimmed().split(' '); + Q_ASSERT(uuid.length()>0); + + QString revision = uuid[0].trimmed(); + tags.insert(tag, revision); + } + return true; +} + +//------------------------------------------------------------------------------ +bool Fossil::tagNew(const QString& name, const QString& revision) +{ + QStringList res; + + if(!runFossil(QStringList() << "tag" << "add" << name << revision, &res)) + return false; + return true; +} + +//------------------------------------------------------------------------------ +bool Fossil::tagDelete(const QString& name, const QString &revision) +{ + QStringList res; + + if(!runFossil(QStringList() << "tag" << "cancel" << "tag:"+name << revision, &res)) + return false; + + return true; +} + +//------------------------------------------------------------------------------ +bool Fossil::branchList(QStringList& branches, QStringList& activeBranches) +{ + branches.clear(); + activeBranches.clear(); + QStringList res; + + if(!runFossil(QStringList() << "branch" , &res, RUNFLAGS_SILENT_ALL)) + return false; + + foreach(const QString &line, res) + { + QString name = line.trimmed(); + + if(name.isEmpty()) + continue; + + // Active branches start with a start + int active_index = name.indexOf('*'); + bool is_active = (active_index != -1) && active_index==0; + + // Strip + if(is_active) + { + name = name.mid(is_active+1).trimmed(); + activeBranches.append(name); + } + else + branches.append(name); + } + + branches.sort(); + activeBranches.sort(); + return true; +} + +//------------------------------------------------------------------------------ +bool Fossil::branchNew(const QString& name, const QString& revisionBasis, bool isPrivate) +{ + QStringList params; + + params <<"branch" << "new" << name << revisionBasis; + + if(isPrivate) + params << "--private"; + + QStringList res; + + if(!runFossil(params, &res)) + return false; + return true; +} + +//------------------------------------------------------------------------------ +bool Fossil::branchMerge(QStringList &res, const QString& revision, bool integrate, bool force, bool testOnly) +{ + QStringList params; + + params <<"merge"; + + if(integrate) + params << "--integrate"; + + if(force) + params << "--force"; + + if(testOnly) + params << "--dry-run"; + + params << revision; + + if(!runFossil(params, &res)) + return false; + return true; +} //------------------------------------------------------------------------------ static QString ParseFossilQuery(QString line) { // Extract question @@ -563,15 +736,41 @@ QByteArray input; process.getLogAndClear(input); #ifdef QT_DEBUG // Log fossil output in debug builds - if(!input.isEmpty()) + if(!input.isEmpty() && (runFlags & RUNFLAGS_DEBUG) ) qDebug() << "[" << ++input_index << "] '" << input.data() << "'\n"; #endif buffer += decoder->toUnicode(input); +#if 0 // Keep this for now to simulate bad parses + if(runFlags & RUNFLAGS_DEBUG) + { + static int debug=1; + if(debug==1) + { + buffer = " 4: [df2233aabe09ef] on 2013-02-03 02:51:33\n" + " History\n" + " 5: [df2233aabe09ef] on 2013-02-15 03:55:37\n" + " "; + debug++; + } + else if(debug==2) + { + buffer = " Diff\n" + " "; + debug++; + } + else + buffer=""; + } +#endif + #ifdef QT_DEBUG // breakpoint + //if(buffer.indexOf("SQLITE_CANTOPEN")!=-1) + // qDebug() << "Breakpoint\n"; + #endif QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); if(buffer.isEmpty()) continue; @@ -591,13 +790,16 @@ // Detect previous line if(last_line_start>0) { int before_last_line_start = buffer.lastIndexOf(EOL_MARK, last_line_start-1); - // No line before ? + + // No new-line before ? if(before_last_line_start==-1) before_last_line_start = 0; // Use entire line + else + ++before_last_line_start; // Skip new-line // Extract previous line before_last_line = buffer.mid(before_last_line_start, last_line_start-before_last_line_start); } } @@ -617,15 +819,22 @@ // Flush all complete lines to the log and output QStringList log_lines = buffer.left(last_line_start).split(EOL_MARK); for(int l=0; l0 && l==log_lines.length()-1 && buffer[buffer.length()-1] != EOL_MARK ) + // Skip last line if we have a query. This will be handled manually further down + if(have_query && l==log_lines.length()-1) continue; QString line = log_lines[l].trimmed(); + + #ifdef QT_DEBUG + if(runFlags & RUNFLAGS_DEBUG) + { + qDebug() << "LINE: " << line << "\n"; + } + #endif if(line.isEmpty()) continue; if(output) @@ -791,18 +1000,68 @@ log("> fossil ui
", true); log(QObject::tr("Starting Fossil browser UI. Please wait.")+"\n"); QString fossil = getFossilPath(); - fossilUI.start(fossil, QStringList() << "server" << "--localauth" << "-P" << httpPort ); + QStringList params; + params << "server" << "--localauth"; + + if(!httpPort.isEmpty()) + params << "-P" << httpPort; + + fossilUI.start(getFossilPath(), params); if(!fossilUI.waitForStarted() || fossilUI.state()!=QProcess::Running) { log(QObject::tr("Could not start Fossil executable '%s'").arg(fossil)+"\n"); return false; } +#ifdef Q_OS_WIN + QTextCodec *codec = QTextCodec::codecForName("UTF-8"); +#else + QTextCodec *codec = QTextCodec::codecForLocale(); +#endif + + Q_ASSERT(codec); + QTextDecoder *decoder = codec->makeDecoder(); + Q_ASSERT(decoder); + + fossilUIPort.clear(); + + // Wait for fossil to report the http port + QString buffer; + while(true) + { + QProcess::ProcessState state = fossilUI.state(); + qint64 bytes_avail = fossilUI.logBytesAvailable(); + + if(state!=QProcess::Running && bytes_avail<1) + break; + + QByteArray input; + fossilUI.getLogAndClear(input); + + buffer += decoder->toUnicode(input); + + // Normalize line endings + buffer = buffer.replace("\r\n", "\n"); + buffer = buffer.replace("\r", "\n"); + + int index = REGEX_PORT.indexIn(buffer); + if(index==-1) + { + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + continue; + } + + // Extract port + fossilUIPort = REGEX_PORT.cap(1).trimmed(); + + // Done parsing + break; + } return true; } //------------------------------------------------------------------------------ void Fossil::stopUI() @@ -814,6 +1073,15 @@ #else fossilUI.terminate(); #endif } fossilUI.close(); + fossilUIPort.clear(); +} + +//------------------------------------------------------------------------------ +QString Fossil::getUIHttpAddress() const +{ + if(fossilUIPort.isEmpty()) + return QString(); + return "http://127.0.0.1:"+fossilUIPort; } Index: src/Fossil.h ================================================================== --- src/Fossil.h +++ src/Fossil.h @@ -1,12 +1,13 @@ #ifndef FOSSIL_H #define FOSSIL_H class QStringList; #include +#include #include -#include +#include "LoggedProcess.h" #include "Utils.h" typedef QMap stashmap_t; #define PATH_SEPARATOR "/" @@ -15,11 +16,12 @@ { RUNFLAGS_NONE = 0<<0, RUNFLAGS_SILENT_INPUT = 1<<0, RUNFLAGS_SILENT_OUTPUT = 1<<1, RUNFLAGS_SILENT_ALL = RUNFLAGS_SILENT_INPUT | RUNFLAGS_SILENT_OUTPUT, - RUNFLAGS_DETACHED = 1<<2 + RUNFLAGS_DETACHED = 1<<2, + RUNFLAGS_DEBUG = 1<<3, }; enum RepoStatus { REPO_OK, @@ -81,20 +83,22 @@ bool closeRepository(); bool pushRepository(); bool pullRepository(); bool cloneRepository(const QString &repository, const QUrl &url, const QUrl &proxyUrl); bool undoRepository(QStringList& result, bool explainOnly); - bool updateRepository(QStringList& result, bool explainOnly); + bool updateRepository(QStringList& result, const QString& revision, bool explainOnly); bool getFossilVersion(QString &version); bool uiRunning() const; bool startUI(const QString &httpPort); void stopUI(); bool listFiles(QStringList &files); + bool status(QStringList& result); + bool diffFile(const QString &repoFile); - bool commitFiles(const QStringList &fileList, const QString &comment); + bool commitFiles(const QStringList &fileList, const QString &comment, const QString& newBranchName, bool isPrivateBranch); bool addFiles(const QStringList& fileList); bool removeFiles(const QStringList& fileList, bool deleteLocal); bool revertFiles(const QStringList& fileList); bool renameFile(const QString& beforePath, const QString& afterPath, bool renameLocal); bool getFossilSettings(QStringList& result); @@ -108,10 +112,24 @@ bool stashDrop(const QString& name); bool stashDiff(const QString& name); void abortOperation() { operationAborted = true; } + bool tagList(QStringMap& tags); + bool tagNew(const QString& name, const QString& revision); + bool tagDelete(const QString& name, const QString& revision); + + bool branchList(QStringList& branches, QStringList& activeBranches); + bool branchNew(const QString& name, const QString& revisionBasis, bool isPrivate=false); + bool branchMerge(QStringList& res, const QString& revision, bool integrate, bool force, bool testOnly); + + const QString &getCurrentRevision() const { return currentRevision; } + const QStringList &getCurrentTags() const { return currentTags; } + + const QString &getUIHttpPort() const { return fossilUIPort; } + QString getUIHttpAddress() const; + private: void log(const QString &text, bool isHTML=false) { if(uiCallback) uiCallback->logText(text, isHTML); @@ -123,10 +141,13 @@ UICallback *uiCallback; QString currentWorkspace; QString fossilPath; // The value from the settings QString repositoryFile; QString projectName; - QProcess fossilUI; + QString currentRevision; + QStringList currentTags; + LoggedProcess fossilUI; + QString fossilUIPort; }; #endif // FOSSIL_H ADDED src/FslSettingsDialog.cpp Index: src/FslSettingsDialog.cpp ================================================================== --- /dev/null +++ src/FslSettingsDialog.cpp @@ -0,0 +1,72 @@ +#include "FslSettingsDialog.h" +#include "ui_FslSettingsDialog.h" +#include "Utils.h" + +#include + +/////////////////////////////////////////////////////////////////////////////// +FslSettingsDialog::FslSettingsDialog(QWidget *parent, Settings &_settings) : + QDialog(parent, Qt::Sheet), + ui(new Ui::FslSettingsDialog), + settings(&_settings) +{ + ui->setupUi(this); + + ui->lineUIPort->setText(settings->GetValue(FOSSIL_SETTING_HTTP_PORT).toString()); + + // Global Settings + ui->lineGDiffCommand->setText(settings->GetFossilValue(FOSSIL_SETTING_GDIFF_CMD).toString()); + ui->lineGMergeCommand->setText(settings->GetFossilValue(FOSSIL_SETTING_GMERGE_CMD).toString()); + ui->lineProxy->setText(settings->GetFossilValue(FOSSIL_SETTING_PROXY_URL).toString()); + + // Repository Settings + ui->lineRemoteURL->setText(settings->GetFossilValue(FOSSIL_SETTING_REMOTE_URL).toString()); + ui->lineIgnore->setText(settings->GetFossilValue(FOSSIL_SETTING_IGNORE_GLOB).toString()); + ui->lineIgnoreCRNL->setText(settings->GetFossilValue(FOSSIL_SETTING_CRNL_GLOB).toString()); +} + +//----------------------------------------------------------------------------- +FslSettingsDialog::~FslSettingsDialog() +{ + delete ui; +} + +//----------------------------------------------------------------------------- +bool FslSettingsDialog::run(QWidget *parent, Settings &settings) +{ + FslSettingsDialog dlg(parent, settings); + return dlg.exec() == QDialog::Accepted; +} + +//----------------------------------------------------------------------------- +void FslSettingsDialog::on_buttonBox_accepted() +{ + settings->SetValue(FOSSIL_SETTING_HTTP_PORT, ui->lineUIPort->text()); + + settings->SetFossilValue(FOSSIL_SETTING_GDIFF_CMD, ui->lineGDiffCommand->text()); + settings->SetFossilValue(FOSSIL_SETTING_GMERGE_CMD, ui->lineGMergeCommand->text()); + settings->SetFossilValue(FOSSIL_SETTING_PROXY_URL, ui->lineProxy->text()); + + settings->SetFossilValue(FOSSIL_SETTING_REMOTE_URL, ui->lineRemoteURL->text()); + settings->SetFossilValue(FOSSIL_SETTING_IGNORE_GLOB, ui->lineIgnore->text()); + settings->SetFossilValue(FOSSIL_SETTING_CRNL_GLOB, ui->lineIgnoreCRNL->text()); + + settings->ApplyEnvironment(); +} + +//----------------------------------------------------------------------------- +void FslSettingsDialog::on_btnSelectFossilGDiff_clicked() +{ + QString path = SelectExe(this, tr("Select Graphical Diff application")); + if(!path.isEmpty()) + ui->lineGDiffCommand->setText(QDir::toNativeSeparators(path)); +} + +//----------------------------------------------------------------------------- +void FslSettingsDialog::on_btnSelectGMerge_clicked() +{ + QString path = SelectExe(this, tr("Select Graphical Merge application")); + if(!path.isEmpty()) + ui->lineGMergeCommand->setText(path); +} + ADDED src/FslSettingsDialog.h Index: src/FslSettingsDialog.h ================================================================== --- /dev/null +++ src/FslSettingsDialog.h @@ -0,0 +1,33 @@ +#ifndef FSLSETTINGSDIALOG_H +#define FSLSETTINGSDIALOG_H + +#include +#include "Settings.h" + +namespace Ui { + class FslSettingsDialog; +} + +class FslSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit FslSettingsDialog(QWidget *parent, Settings &_settings); + ~FslSettingsDialog(); + + static bool run(QWidget *parent, Settings &_settings); + + +private slots: + void on_buttonBox_accepted(); + void on_btnSelectFossilGDiff_clicked(); + void on_btnSelectGMerge_clicked(); + +private: + + Ui::FslSettingsDialog *ui; + Settings *settings; +}; + +#endif // FSLSETTINGSDIALOG_H Index: src/MainWindow.cpp ================================================================== --- src/MainWindow.cpp +++ src/MainWindow.cpp @@ -4,20 +4,26 @@ #include #include #include #include #include -#include #include #include #include +#include #include #include +#include "SettingsDialog.h" +#include "FslSettingsDialog.h" +#include "SearchBox.h" #include "CommitDialog.h" #include "FileActionDialog.h" #include "CloneDialog.h" +#include "RevisionDialog.h" #include "Utils.h" + +#define REVISION_LATEST "Latest revision" //----------------------------------------------------------------------------- enum { COLUMN_STATUS, @@ -33,39 +39,55 @@ TAB_BROWSER }; enum { - REPODIRMODEL_ROLE_PATH = Qt::UserRole+1 + ROLE_WORKSPACE_ITEM = Qt::UserRole+1 +}; + +struct WorkspaceItem +{ + enum + { + TYPE_UNKNOWN, + TYPE_WORKSPACE, + TYPE_FOLDER, + TYPE_STASHES, + TYPE_STASH, + TYPE_BRANCHES, + TYPE_BRANCH, + TYPE_TAGS, + TYPE_TAG, + TYPE_REMOTES, + TYPE_SETTINGS + }; + + WorkspaceItem() + : Type(TYPE_UNKNOWN) + { + } + + WorkspaceItem(int type, const QString &value) + : Type(type), Value(value) + { + } + + WorkspaceItem(const WorkspaceItem &other) + { + Type = other.Type; + Value = other.Value; + } + + int Type; + QString Value; + + operator QVariant() const + { + return QVariant::fromValue(*this); + } }; - -//----------------------------------------------------------------------------- -typedef QMap QStringMap; -static QStringMap MakeKeyValues(QStringList lines) -{ - QStringMap res; - - foreach(QString l, lines) - { - l = l.trimmed(); - int index = l.indexOf(' '); - - QString key; - QString value; - if(index!=-1) - { - key = l.left(index).trimmed(); - value = l.mid(index).trimmed(); - } - else - key = l; - - res.insert(key, value); - } - return res; -} - +Q_DECLARE_METATYPE(WorkspaceItem) /////////////////////////////////////////////////////////////////////////////// MainWindow::MainWindow(Settings &_settings, QWidget *parent, QString *workspacePath) : QMainWindow(parent), ui(new Ui::MainWindow), @@ -106,31 +128,53 @@ ui->fileTableView->horizontalHeader()->setSectionsMovable(true); #endif ui->fileTableView->horizontalHeader()->setStretchLastSection(true); // workspaceTreeView - ui->workspaceTreeView->setModel(&getWorkspace().getDirModel()); + ui->workspaceTreeView->setModel(&getWorkspace().getTreeModel()); + + header.clear(); + header << tr("Workspace"); + getWorkspace().getTreeModel().setHorizontalHeaderLabels(header); + connect( ui->workspaceTreeView->selectionModel(), SIGNAL( selectionChanged(const QItemSelection &, const QItemSelection &) ), SLOT( onWorkspaceTreeViewSelectionChanged(const QItemSelection &, const QItemSelection &) ), Qt::DirectConnection ); - ui->workspaceTreeView->addAction(ui->actionCommit); - ui->workspaceTreeView->addAction(ui->actionOpenFolder); - ui->workspaceTreeView->addAction(ui->actionAdd); - ui->workspaceTreeView->addAction(ui->actionRevert); - ui->workspaceTreeView->addAction(ui->actionDelete); - ui->workspaceTreeView->addAction(separator); - ui->workspaceTreeView->addAction(ui->actionRenameFolder); - ui->workspaceTreeView->addAction(ui->actionOpenFolder); - - // StashView - ui->stashTableView->setModel(&getWorkspace().getStashModel()); - ui->stashTableView->addAction(ui->actionApplyStash); - ui->stashTableView->addAction(ui->actionDiffStash); - ui->stashTableView->addAction(ui->actionDeleteStash); - ui->stashTableView->horizontalHeader()->setSortIndicatorShown(false); + // Workspace Menus + menuWorkspace = new QMenu(this); + menuWorkspace->addAction(ui->actionCommit); + menuWorkspace->addAction(ui->actionOpenFolder); + menuWorkspace->addAction(ui->actionAdd); + menuWorkspace->addAction(ui->actionRevert); + menuWorkspace->addAction(ui->actionDelete); + menuWorkspace->addAction(separator); + menuWorkspace->addAction(ui->actionRenameFolder); + menuWorkspace->addAction(ui->actionOpenFolder); + + // StashMenu + menuStashes = new QMenu(this); + menuStashes->addAction(ui->actionCreateStash); + menuStashes->addAction(separator); + menuStashes->addAction(ui->actionApplyStash); + menuStashes->addAction(ui->actionDiffStash); + menuStashes->addAction(ui->actionDeleteStash); + + // TagsMenu + menuTags = new QMenu(this); + menuTags->addAction(ui->actionCreateTag); + menuTags->addAction(separator); + menuTags->addAction(ui->actionDeleteTag); + menuTags->addAction(ui->actionUpdate); + + // BranchesMenu + menuBranches = new QMenu(this); + menuBranches->addAction(ui->actionCreateBranch); + menuBranches->addAction(separator); + menuBranches->addAction(ui->actionMergeBranch); + menuBranches->addAction(ui->actionUpdate); // Recent Workspaces // Locate a sequence of two separator actions in file menu QList file_actions = ui->menuFile->actions(); QAction *recent_sep=0; @@ -152,20 +196,30 @@ ui->menuFile->insertAction(recent_sep, recentWorkspaceActs[i]); } // TabWidget ui->tabWidget->setCurrentIndex(TAB_LOG); + + lblRevision = new QLabel(); + ui->statusBar->insertPermanentWidget(0, lblRevision); + lblRevision->setVisible(true); + + lblTags = new QLabel(); + ui->statusBar->insertPermanentWidget(1, lblTags); + lblTags->setVisible(true); + // Construct ProgressBar progressBar = new QProgressBar(); progressBar->setMinimum(0); progressBar->setMaximum(0); progressBar->setMaximumSize(170, 16); progressBar->setAlignment(Qt::AlignCenter); progressBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); - ui->statusBar->insertPermanentWidget(0, progressBar); + ui->statusBar->insertPermanentWidget(2, progressBar); progressBar->setVisible(false); + #ifdef Q_OS_MACX // Native applications on OSX don't have menu icons foreach(QAction *a, ui->menuBar->actions()) a->setIconVisibleInMenu(false); @@ -176,10 +230,34 @@ abortShortcut = new QShortcut(QKeySequence("Escape"), this); abortShortcut->setContext(Qt::ApplicationShortcut); abortShortcut->setEnabled(false); connect(abortShortcut, SIGNAL(activated()), this, SLOT(onAbort())); + // Searchbox + // Add spacer to pad to right + QWidget* spacer = new QWidget(); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + ui->mainToolBar->addWidget(spacer); + + // Search shortcut + searchShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); + searchShortcut->setContext(Qt::ApplicationShortcut); + searchShortcut->setEnabled(true); + connect(searchShortcut, SIGNAL(activated()), this, SLOT(onSearch())); + + + // Create SearchBox + searchBox = new SearchBox(this); + searchBox->setPlaceholderText(tr("Find (%0)").arg(searchShortcut->key().toString())); + searchBox->setMaximumWidth(450); + ui->mainToolBar->addWidget(searchBox); + + connect( searchBox, + SIGNAL( textChanged(const QString&)), + SLOT( onSearchBoxTextChanged(const QString&)), + Qt::DirectConnection ); + viewMode = VIEWMODE_TREE; uiCallback.init(this); // Need to be before applySettings which sets the last workspace fossil().Init(&uiCallback); @@ -319,10 +397,11 @@ return false; } // Select the Root of the tree to update the file view selectRootDir(); + searchBox->clear(); return true; } //------------------------------------------------------------------------------ void MainWindow::on_actionOpenRepository_triggered() @@ -477,31 +556,42 @@ //------------------------------------------------------------------------------ void MainWindow::enableActions(bool on) { - ui->actionCommit->setEnabled(on); - ui->actionDiff->setEnabled(on); - ui->actionAdd->setEnabled(on); - ui->actionDelete->setEnabled(on); - ui->actionPush->setEnabled(on); - ui->actionPull->setEnabled(on); - ui->actionRename->setEnabled(on); - ui->actionHistory->setEnabled(on); - ui->actionFossilUI->setEnabled(on); - ui->actionRevert->setEnabled(on); - ui->actionTimeline->setEnabled(on); - ui->actionOpenFile->setEnabled(on); - ui->actionOpenContaining->setEnabled(on); - ui->actionUndo->setEnabled(on); - ui->actionUpdate->setEnabled(on); - ui->actionOpenFolder->setEnabled(on); - ui->actionRenameFolder->setEnabled(on); - ui->actionNewStash->setEnabled(on); - ui->actionDeleteStash->setEnabled(on); - ui->actionDiffStash->setEnabled(on); - ui->actionApplyStash->setEnabled(on); + QAction *actions[] = { + ui->actionCommit, + ui->actionDiff, + ui->actionAdd, + ui->actionDelete, + ui->actionPush, + ui->actionPull, + ui->actionRename, + ui->actionHistory, + ui->actionFossilUI, + ui->actionRevert, + ui->actionTimeline, + ui->actionOpenFile, + ui->actionOpenContaining, + ui->actionUndo, + ui->actionUpdate, + ui->actionOpenFolder, + ui->actionRenameFolder, + ui->actionCreateStash, + ui->actionDeleteStash, + ui->actionDiffStash, + ui->actionApplyStash, + ui->actionDeleteStash, + ui->actionCreateTag, + ui->actionDeleteTag, + ui->actionCreateBranch, + ui->actionMergeBranch, + }; + + for(size_t i=0; isetEnabled(on); + } //------------------------------------------------------------------------------ bool MainWindow::refresh() { QString title = "Fuel"; @@ -512,20 +602,20 @@ if(st==REPO_NOT_FOUND) { setStatus(tr("No workspace detected.")); enableActions(false); getWorkspace().getFileModel().removeRows(0, getWorkspace().getFileModel().rowCount()); - getWorkspace().getDirModel().clear(); + getWorkspace().getTreeModel().removeRows(0, getWorkspace().getFileModel().rowCount()); setWindowTitle(title); return false; } else if(st==REPO_OLD_SCHEMA) { setStatus(tr("Old repository schema detected. Consider running 'fossil rebuild'")); enableActions(false); getWorkspace().getFileModel().removeRows(0, getWorkspace().getFileModel().rowCount()); - getWorkspace().getDirModel().clear(); + getWorkspace().getTreeModel().removeRows(0, getWorkspace().getFileModel().rowCount()); setWindowTitle(title); return true; } loadFossilSettings(); @@ -552,20 +642,32 @@ ui->actionViewUnchanged->isChecked(), settings.GetFossilValue(FOSSIL_SETTING_IGNORE_GLOB).toString(), uiCallback, operationAborted ); - updateDirView(); + updateWorkspaceView(); updateFileView(); - updateStashView(); + + // Build default versions list + versionList.clear(); + versionList += getWorkspace().getBranches(); + versionList += getWorkspace().getTags().keys(); + + selectedTags.clear(); + selectedBranches.clear(); setBusy(false); setStatus(""); + lblRevision->setText(tr("Revision: %0").arg(fossil().getCurrentRevision())); + lblRevision->setVisible(true); + + lblTags->setText(tr("Tags: %0").arg(fossil().getCurrentTags().join(' '))); + lblTags->setVisible(true); } //------------------------------------------------------------------------------ -static void addPathToTree(QStandardItem &root, const QString &path) +static void addPathToTree(QStandardItem &root, const QString &path, const QIcon &folderIcon) { QStringList dirs = path.split('/'); QStandardItem *parent = &root; QString fullpath; @@ -587,44 +689,156 @@ } } if(!found) // Generate it { - QStandardItem *child = new QStandardItem(QIcon(":icons/icons/Folder-01.png"), dir); - child->setData(fullpath); // keep the full path to simplify selection + QStandardItem *child = new QStandardItem(folderIcon, dir); + child->setData(WorkspaceItem(WorkspaceItem::TYPE_FOLDER, fullpath), ROLE_WORKSPACE_ITEM); parent->appendRow(child); parent = child; } fullpath += '/'; } } //------------------------------------------------------------------------------ -void MainWindow::updateDirView() -{ - // Directory View - getWorkspace().getDirModel().clear(); - - QStringList header; - header << tr("Folders"); - getWorkspace().getDirModel().setHorizontalHeaderLabels(header); - - QStandardItem *root = new QStandardItem(QIcon(":icons/icons/My Documents-01.png"), fossil().getProjectName()); - root->setData(""); // Empty Path - root->setEditable(false); - - getWorkspace().getDirModel().appendRow(root); - for(stringset_t::iterator it = getWorkspace().getPaths().begin(); it!=getWorkspace().getPaths().end(); ++it) - { - const QString &dir = *it; - if(dir.isEmpty()) - continue; - - addPathToTree(*root, dir); - } - ui->workspaceTreeView->expandToDepth(0); - ui->workspaceTreeView->sortByColumn(0, Qt::AscendingOrder); +void MainWindow::updateWorkspaceView() +{ + // Record expanded tree-node names, and selection + name_modelindex_map_t name_map; + BuildNameToModelIndex(name_map, getWorkspace().getTreeModel()); + stringset_t expanded_items; + stringset_t selected_items; + + const QItemSelection selection = ui->workspaceTreeView->selectionModel()->selection(); + + for(name_modelindex_map_t::const_iterator it=name_map.begin(); it!=name_map.end(); ++it) + { + const QModelIndex mi = it.value(); + if(ui->workspaceTreeView->isExpanded(mi)) + expanded_items.insert(it.key()); + + if(selection.contains(mi)) + selected_items.insert(it.key()); + } + + // Clear content except headers + getWorkspace().getTreeModel().removeRows(0, getWorkspace().getTreeModel().rowCount()); + + QStandardItem *workspace = new QStandardItem(getInternalIcon(":icons/icon-item-folder"), tr("Files") ); + workspace->setData(WorkspaceItem(WorkspaceItem::TYPE_WORKSPACE, ""), ROLE_WORKSPACE_ITEM); + workspace->setEditable(false); + + getWorkspace().getTreeModel().appendRow(workspace); + if(viewMode == VIEWMODE_TREE) + { + for(stringset_t::iterator it = getWorkspace().getPaths().begin(); it!=getWorkspace().getPaths().end(); ++it) + { + const QString &dir = *it; + if(dir.isEmpty()) + continue; + + addPathToTree(*workspace, dir, getInternalIcon(":icons/icon-item-folder")); + } + + // Expand root folder + ui->workspaceTreeView->setExpanded(workspace->index(), true); + } + + // Branches + QStandardItem *branches = new QStandardItem(getInternalIcon(":icons/icon-item-branch"), tr("Branches")); + branches->setData(WorkspaceItem(WorkspaceItem::TYPE_BRANCHES, ""), ROLE_WORKSPACE_ITEM); + branches->setEditable(false); + getWorkspace().getTreeModel().appendRow(branches); + foreach(const QString &branch_name, getWorkspace().getBranches()) + { + QStandardItem *branch = new QStandardItem(getInternalIcon(":icons/icon-item-branch"), branch_name); + branch->setData(WorkspaceItem(WorkspaceItem::TYPE_BRANCH, branch_name), ROLE_WORKSPACE_ITEM); + + bool active = fossil().getCurrentTags().contains(branch_name); + if(active) + { + QFont font = branch->font(); + font.setBold(true); + branch->setFont(font); + } + branches->appendRow(branch); + } + + // Tags + QStandardItem *tags = new QStandardItem(getInternalIcon(":icons/icon-item-tag"), tr("Tags")); + tags->setData(WorkspaceItem(WorkspaceItem::TYPE_TAGS, ""), ROLE_WORKSPACE_ITEM); + tags->setEditable(false); + getWorkspace().getTreeModel().appendRow(tags); + for(QStringMap::const_iterator it=getWorkspace().getTags().begin(); it!=getWorkspace().getTags().end(); ++it) + { + const QString &tag_name = it.key(); + + + QStandardItem *tag = new QStandardItem(getInternalIcon(":icons/icon-item-tag"), tag_name); + tag->setData(WorkspaceItem(WorkspaceItem::TYPE_TAG, tag_name), ROLE_WORKSPACE_ITEM); + + bool active = fossil().getCurrentTags().contains(tag_name); + if(active) + { + QFont font = tag->font(); + font.setBold(true); + tag->setFont(font); + } + tags->appendRow(tag); + } + + // FIXME: Unique Icon name + // Stashes + QStandardItem *stashes = new QStandardItem(getInternalIcon(":icons/icon-action-repo-open"), tr("Stashes")); + stashes->setData(WorkspaceItem(WorkspaceItem::TYPE_STASHES, ""), ROLE_WORKSPACE_ITEM); + stashes->setEditable(false); + getWorkspace().getTreeModel().appendRow(stashes); + for(stashmap_t::const_iterator it= getWorkspace().getStashes().begin(); it!=getWorkspace().getStashes().end(); ++it) + { + QStandardItem *stash = new QStandardItem(getInternalIcon(":icons/icon-action-repo-open"), it.key()); + stash->setData(WorkspaceItem(WorkspaceItem::TYPE_STASH, it.value()), ROLE_WORKSPACE_ITEM); + stashes->appendRow(stash); + } + +#if 0 + // Remotes + QStandardItem *remotes = new QStandardItem(getInternalIcon(":icons/icon-item-remote"), tr("Remotes")); + remotes->setData(WorkspaceItem(WorkspaceItem::TYPE_REMOTES, ""), ROLE_WORKSPACE_ITEM); + remotes->setEditable(false); + getWorkspace().getTreeModel().appendRow(remotes); +#endif + +#if 0 // Unimplemented for now + // Settings + QStandardItem *settings = new QStandardItem(getInternalIcon(":icons/icon-action-settings"), tr("Settings")); + settings->setData(WorkspaceItem(WorkspaceItem::TYPE_SETTINGS, ""), ROLE_WORKSPACE_ITEM); + settings->setEditable(false); + getWorkspace().getTreeModel().appendRow(settings); +#endif + + // Expand previously selected nodes + name_map.clear(); + BuildNameToModelIndex(name_map, getWorkspace().getTreeModel()); + + for(stringset_t::const_iterator it=expanded_items.begin(); it!=expanded_items.end(); ++it) + { + name_modelindex_map_t::const_iterator mi_it = name_map.find(*it); + if(mi_it!=name_map.end()) + ui->workspaceTreeView->setExpanded(mi_it.value(), true); + } + + // Select previous selected item + for(stringset_t::const_iterator it=selected_items.begin(); it!=selected_items.end(); ++it) + { + name_modelindex_map_t::const_iterator mi_it = name_map.find(*it); + if(mi_it!=name_map.end()) + { + const QModelIndex &mi = mi_it.value(); + ui->workspaceTreeView->selectionModel()->select(mi, QItemSelectionModel::Select); + } + } } //------------------------------------------------------------------------------ void MainWindow::updateFileView() { @@ -632,65 +846,78 @@ getWorkspace().getFileModel().removeRows(0, getWorkspace().getFileModel().rowCount()); struct { WorkspaceFile::Type type; QString text; const char *icon; } stats[] = { - { WorkspaceFile::TYPE_EDITTED, tr("Edited"), ":icons/icons/Button Blank Yellow-01.png" }, - { WorkspaceFile::TYPE_UNCHANGED, tr("Unchanged"), ":icons/icons/Button Blank Green-01.png" }, - { WorkspaceFile::TYPE_ADDED, tr("Added"), ":icons/icons/Button Add-01.png" }, - { WorkspaceFile::TYPE_DELETED, tr("Deleted"), ":icons/icons/Button Close-01.png" }, - { WorkspaceFile::TYPE_RENAMED, tr("Renamed"), ":icons/icons/Button Reload-01.png" }, - { WorkspaceFile::TYPE_MISSING, tr("Missing"), ":icons/icons/Button Help-01.png" }, - { WorkspaceFile::TYPE_CONFLICTED, tr("Conflicted"), ":icons/icons/Button Blank Red-01.png" }, + { WorkspaceFile::TYPE_EDITTED, tr("Edited"), ":icons/icon-item-edited" }, + { WorkspaceFile::TYPE_UNCHANGED, tr("Unchanged"), ":icons/icon-item-unchanged" }, + { WorkspaceFile::TYPE_ADDED, tr("Added"), ":icons/icon-item-added" }, + { WorkspaceFile::TYPE_DELETED, tr("Deleted"), ":icons/icon-item-deleted" }, + { WorkspaceFile::TYPE_RENAMED, tr("Renamed"), ":icons/icon-item-renamed" }, + { WorkspaceFile::TYPE_MISSING, tr("Missing"), ":icons/icon-item-missing" }, + { WorkspaceFile::TYPE_CONFLICTED, tr("Conflicted"), ":icons/icon-item-conflicted" }, + { WorkspaceFile::TYPE_MERGED, tr("Merged"), ":icons/icon-item-edited" }, }; - QFileIconProvider icon_provider; - bool display_path = viewMode==VIEWMODE_LIST || selectedDirs.count() > 1; + + const QString &status_unknown = QString(tr("Unknown")); + const QString &search_text = searchBox->text(); size_t item_id=0; for(Workspace::filemap_t::iterator it = getWorkspace().getFiles().begin(); it!=getWorkspace().getFiles().end(); ++it) { const WorkspaceFile &e = *it.value(); - QString path = e.getPath(); + const QString &path = e.getPath(); + const QString &file_path = e.getFilePath(); + QString native_file_path = QDir::toNativeSeparators(file_path); + + // Apply filter if available + if(!search_text.isEmpty() && !native_file_path.contains(search_text, Qt::CaseInsensitive)) + continue; // In Tree mode, filter all items not included in the current dir if(viewMode==VIEWMODE_TREE && !selectedDirs.contains(path)) continue; // Status Column - QString status_text = QString(tr("Unknown")); - const char *status_icon_path= ":icons/icons/Button Blank Gray-01.png"; // Default icon + const QString *status_text = &status_unknown; + const char *status_icon_path= ":icons/icon-item-unknown"; // Default icon for(size_t t=0; tsetToolTip(status_text); + QStandardItem *status = new QStandardItem(getInternalIcon(status_icon_path), *status_text); + status->setToolTip(*status_text); getWorkspace().getFileModel().setItem(item_id, COLUMN_STATUS, status); QFileInfo finfo = e.getFileInfo(); - QIcon icon = icon_provider.icon(finfo); + QString icon_type = iconProvider.type(finfo); + + if(!iconCache.contains(icon_type)) + iconCache.insert(icon_type, iconProvider.icon(finfo)); + + const QIcon *icon = &iconCache[icon_type]; QStandardItem *filename_item = 0; getWorkspace().getFileModel().setItem(item_id, COLUMN_PATH, new QStandardItem(path)); if(display_path) - filename_item = new QStandardItem(icon, QDir::toNativeSeparators(e.getFilePath())); + filename_item = new QStandardItem(*icon, native_file_path); else - filename_item = new QStandardItem(icon, e.getFilename()); + filename_item = new QStandardItem(*icon, e.getFilename()); Q_ASSERT(filename_item); // Keep the path in the user data - filename_item->setData(e.getFilePath()); + filename_item->setData(file_path); getWorkspace().getFileModel().setItem(item_id, COLUMN_FILENAME, filename_item); getWorkspace().getFileModel().setItem(item_id, COLUMN_EXTENSION, new QStandardItem(finfo.suffix())); getWorkspace().getFileModel().setItem(item_id, COLUMN_MODIFIED, new QStandardItem(finfo.lastModified().toString(Qt::SystemLocaleShortDate))); @@ -698,29 +925,10 @@ } ui->fileTableView->resizeRowsToContents(); } -//------------------------------------------------------------------------------ -void MainWindow::updateStashView() -{ - getWorkspace().getStashModel().clear(); - - QStringList header; - header << tr("Stashes"); - getWorkspace().getStashModel().setHorizontalHeaderLabels(header); - - for(stashmap_t::iterator it=getWorkspace().getStashes().begin(); it!=getWorkspace().getStashes().end(); ++it) - { - QStandardItem *item = new QStandardItem(it.key()); - item->setToolTip(it.key()); - getWorkspace().getStashModel().appendRow(item); - } - ui->stashTableView->resizeColumnsToContents(); - ui->stashTableView->resizeRowsToContents(); -} - //------------------------------------------------------------------------------ void MainWindow::log(const QString &text, bool isHTML) { QTextCursor c = ui->textBrowser->textCursor(); c.movePosition(QTextCursor::End); @@ -812,18 +1020,14 @@ if(store->contains("ViewIgnored")) ui->actionViewIgnored->setChecked(store->value("ViewIgnored").toBool()); if(store->contains("ViewAsList")) { ui->actionViewAsList->setChecked(store->value("ViewAsList").toBool()); + ui->actionViewAsFolders->setChecked(!store->value("ViewAsList").toBool()); viewMode = store->value("ViewAsList").toBool()? VIEWMODE_LIST : VIEWMODE_TREE; } - ui->workspaceTreeView->setVisible(viewMode == VIEWMODE_TREE); - - if(store->contains("ViewStash")) - ui->actionViewStash->setChecked(store->value("ViewStash").toBool()); - ui->stashTableView->setVisible(ui->actionViewStash->isChecked()); - + //ui->workspaceTreeView->setVisible(viewMode == VIEWMODE_TREE); } //------------------------------------------------------------------------------ void MainWindow::updateSettings() { @@ -858,16 +1062,16 @@ store->setValue("ViewUnknown", ui->actionViewUnknown->isChecked()); store->setValue("ViewModified", ui->actionViewModified->isChecked()); store->setValue("ViewUnchanged", ui->actionViewUnchanged->isChecked()); store->setValue("ViewIgnored", ui->actionViewIgnored->isChecked()); store->setValue("ViewAsList", ui->actionViewAsList->isChecked()); - store->setValue("ViewStash", ui->actionViewStash->isChecked()); } //------------------------------------------------------------------------------ void MainWindow::selectRootDir() { + // FIXME: KKK if(viewMode==VIEWMODE_TREE) { QModelIndex root_index = ui->workspaceTreeView->model()->index(0, 0); ui->workspaceTreeView->selectionModel()->select(root_index, QItemSelectionModel::Select); } @@ -879,11 +1083,11 @@ if(!uiRunning()) ui->actionFossilUI->activate(QAction::Trigger); bool use_internal = settings.GetValue(FUEL_SETTING_WEB_BROWSER).toInt() == 1; - QUrl url = QUrl(getFossilHttpAddress()+fossilUrl); + QUrl url = QUrl(fossil().getUIHttpAddress()+fossilUrl); if(use_internal) { ui->webView->load(url); ui->tabWidget->setCurrentIndex(TAB_BROWSER); @@ -903,15 +1107,20 @@ //------------------------------------------------------------------------------ void MainWindow::getSelectionPaths(stringset_t &paths) { // Determine the directories selected QModelIndexList selection = ui->workspaceTreeView->selectionModel()->selectedIndexes(); - for(QModelIndexList::iterator mi_it = selection.begin(); mi_it!=selection.end(); ++mi_it) + foreach(const QModelIndex &mi, selection) { - const QModelIndex &mi = *mi_it; - QVariant data = getWorkspace().getDirModel().data(mi, REPODIRMODEL_ROLE_PATH); - paths.insert(data.toString()); + QVariant data = mi.model()->data(mi, ROLE_WORKSPACE_ITEM); + Q_ASSERT(data.isValid()); + + WorkspaceItem tv = data.value(); + if(tv.Type != WorkspaceItem::TYPE_FOLDER) + continue; + + paths.insert(tv.Value); } } //------------------------------------------------------------------------------ // Select all workspace files that match the includeMask void MainWindow::getAllFilenames(QStringList &filenames, int includeMask) @@ -1006,29 +1215,27 @@ filenames.append(filename); } } //------------------------------------------------------------------------------ -void MainWindow::getStashViewSelection(QStringList &stashNames, bool allIfEmpty) -{ - QModelIndexList selection = ui->stashTableView->selectionModel()->selectedIndexes(); - if(selection.empty() && allIfEmpty) - { - ui->stashTableView->selectAll(); - selection = ui->stashTableView->selectionModel()->selectedIndexes(); - ui->stashTableView->clearSelection(); - } - - for(QModelIndexList::iterator mi_it = selection.begin(); mi_it!=selection.end(); ++mi_it) - { - const QModelIndex &mi = *mi_it; - - if(mi.column()!=0) +void MainWindow::getSelectionStashes(QStringList &stashNames) +{ + QModelIndexList selection = ui->workspaceTreeView->selectionModel()->selectedIndexes(); + + foreach(const QModelIndex &mi, selection) + { + QVariant data = mi.model()->data(mi, ROLE_WORKSPACE_ITEM); + Q_ASSERT(data.isValid()); + WorkspaceItem tv = data.value(); + + if(tv.Type != WorkspaceItem::TYPE_STASH) continue; - QString name = getWorkspace().getStashModel().data(mi).toString(); + + QString name = mi.model()->data(mi, Qt::DisplayRole).toString(); stashNames.append(name); } + } //------------------------------------------------------------------------------ bool MainWindow::diffFile(const QString &repoFile) { @@ -1047,12 +1254,11 @@ } //------------------------------------------------------------------------------ bool MainWindow::startUI() { - QString port = settings.GetValue(FUEL_SETTING_HTTP_PORT).toString(); - bool started = fossil().startUI(port); + bool started = fossil().startUI(""); ui->actionFossilUI->setChecked(started); return started; } //------------------------------------------------------------------------------ void MainWindow::stopUI() @@ -1158,17 +1364,19 @@ void MainWindow::on_actionCommit_triggered() { QStringList commit_files; getSelectionFilenames(commit_files, WorkspaceFile::TYPE_MODIFIED, true); - if(commit_files.empty()) + if(commit_files.empty() && !getWorkspace().otherChanges()) return; QStringList commit_msgs = settings.GetValue(FUEL_SETTING_COMMIT_MSG).toStringList(); QString msg; - bool aborted = !CommitDialog::run(this, tr("Commit Changes"), commit_files, msg, &commit_msgs); + QString branch_name=""; + bool private_branch = false; + bool aborted = !CommitDialog::runCommit(this, commit_files, msg, commit_msgs, branch_name, private_branch); // Aborted or not we always keep the commit messages. // (This has saved me way too many times on TortoiseSVN) if(commit_msgs.indexOf(msg)==-1) { @@ -1178,11 +1386,11 @@ if(aborted) return; // Since via the commit dialog the user can deselect all files - if(commit_files.empty()) + if(commit_files.empty() && !getWorkspace().otherChanges()) return; // Do commit QStringList files; @@ -1194,11 +1402,11 @@ getAllFilenames(all_modified_files, WorkspaceFile::TYPE_MODIFIED); if(commit_files.size() != all_modified_files.size()) files = commit_files; - fossil().commitFiles(files, msg); + fossil().commitFiles(files, msg, branch_name, private_branch); refresh(); } //------------------------------------------------------------------------------ void MainWindow::on_actionAdd_triggered() @@ -1257,11 +1465,11 @@ //------------------------------------------------------------------------------ void MainWindow::on_actionRevert_triggered() { QStringList modified_files; - getSelectionFilenames(modified_files, WorkspaceFile::TYPE_EDITTED|WorkspaceFile::TYPE_ADDED|WorkspaceFile::TYPE_DELETED|WorkspaceFile::TYPE_MISSING|WorkspaceFile::TYPE_CONFLICTED); + getSelectionFilenames(modified_files, WorkspaceFile::TYPE_MODIFIED); if(modified_files.empty()) return; if(!FileActionDialog::run(this, tr("Revert files"), tr("The following files will be reverted.")+"\n"+tr("Are you sure?"), modified_files)) @@ -1372,28 +1580,17 @@ } //------------------------------------------------------------------------------ void MainWindow::on_actionUpdate_triggered() { - QStringList res; - - // Do test update - if(!fossil().updateRepository(res, true)) - return; - - // Fixme: parse "changes: None. Already up-to-date" and avoid dialog - - if(res.length()==0) - return; - - if(!FileActionDialog::run(this, tr("Update"), tr("The following files will be updated.")+"\n"+tr("Are you sure?"), res)) - return; - - // Do update - fossil().updateRepository(res, false); - - refresh(); + QStringList selected = selectedBranches + selectedTags; + + QString revision; + if(!selected.isEmpty()) + revision = selected.first(); + + updateRevision(revision); } //------------------------------------------------------------------------------ void MainWindow::loadFossilSettings() { @@ -1401,11 +1598,12 @@ QStringList out; if(!fossil().getFossilSettings(out)) return; - QStringMap kv = MakeKeyValues(out); + QStringMap kv; + ParseProperties(kv, out); for(Settings::mappings_t::iterator it=settings.GetMappings().begin(); it!=settings.GetMappings().end(); ++it) { const QString &name = it.key(); Settings::Setting::SettingType type = it->Type; @@ -1444,14 +1642,22 @@ } //------------------------------------------------------------------------------ void MainWindow::on_actionSettings_triggered() { + // Run the dialog + if(!SettingsDialog::run(this, settings)) + return; +} + +//------------------------------------------------------------------------------ +void MainWindow::on_actionFossilSettings_triggered() +{ loadFossilSettings(); // Run the dialog - if(!SettingsDialog::run(this, settings)) + if(!FslSettingsDialog::run(this, settings)) return; // Apply settings for(Settings::mappings_t::iterator it=settings.GetMappings().begin(); it!=settings.GetMappings().end(); ++it) { @@ -1470,10 +1676,11 @@ Q_ASSERT(type == Settings::Setting::TYPE_FOSSIL_GLOBAL || type == Settings::Setting::TYPE_FOSSIL_LOCAL); QString value = it.value().Value.toString(); fossil().setFossilSetting(name, value, type == Settings::Setting::TYPE_FOSSIL_GLOBAL); } + } //------------------------------------------------------------------------------ void MainWindow::on_actionViewModified_triggered() { @@ -1495,46 +1702,73 @@ //------------------------------------------------------------------------------ void MainWindow::on_actionViewIgnored_triggered() { refresh(); } + +//------------------------------------------------------------------------------ +void MainWindow::on_actionViewAll_triggered() +{ + ui->actionViewModified->setChecked(true); + ui->actionViewUnchanged->setChecked(true); + ui->actionViewUnknown->setChecked(true); + ui->actionViewIgnored->setChecked(true); + refresh(); +} //------------------------------------------------------------------------------ void MainWindow::on_actionViewAsList_triggered() { - viewMode = ui->actionViewAsList->isChecked() ? VIEWMODE_LIST : VIEWMODE_TREE; - ui->workspaceTreeView->setVisible(viewMode == VIEWMODE_TREE); + ui->actionViewAsFolders->setChecked(!ui->actionViewAsList->isChecked()); + viewMode = ui->actionViewAsList->isChecked() ? VIEWMODE_LIST : VIEWMODE_TREE; + + updateWorkspaceView(); updateFileView(); } //------------------------------------------------------------------------------ -QString MainWindow::getFossilHttpAddress() +void MainWindow::on_actionViewAsFolders_triggered() { - QString port = settings.GetValue(FUEL_SETTING_HTTP_PORT).toString(); - return "http://127.0.0.1:"+port; + ui->actionViewAsList->setChecked(!ui->actionViewAsFolders->isChecked()); + viewMode = ui->actionViewAsList->isChecked() ? VIEWMODE_LIST : VIEWMODE_TREE; + updateWorkspaceView(); + updateFileView(); } //------------------------------------------------------------------------------ void MainWindow::onWorkspaceTreeViewSelectionChanged(const QItemSelection &/*selected*/, const QItemSelection &/*deselected*/) { - QModelIndexList selection = ui->workspaceTreeView->selectionModel()->selectedIndexes(); - int num_selected = selection.count(); + QModelIndexList indices = ui->workspaceTreeView->selectionModel()->selectedIndexes(); // Do not modify the selection if nothing is selected - if(num_selected==0) + if(indices.empty()) return; - selectedDirs.clear(); + stringset_t new_dirs; + selectedTags.clear(); + selectedBranches.clear(); - for(int i=0; idata(id, ROLE_WORKSPACE_ITEM); + Q_ASSERT(data.isValid()); + WorkspaceItem tv = data.value(); + + if(tv.Type == WorkspaceItem::TYPE_FOLDER || tv.Type == WorkspaceItem::TYPE_WORKSPACE) + new_dirs.insert(tv.Value); + else if(tv.Type == WorkspaceItem::TYPE_TAG) + selectedTags.append(tv.Value); + else if(tv.Type == WorkspaceItem::TYPE_BRANCH) + selectedBranches.append(tv.Value); } - updateFileView(); + // Update the selection if we have any new folders + if(!new_dirs.empty() && viewMode == VIEWMODE_TREE) + { + selectedDirs = new_dirs; + updateFileView(); + } } //------------------------------------------------------------------------------ void MainWindow::on_actionOpenFolder_triggered() { @@ -1548,12 +1782,18 @@ } //------------------------------------------------------------------------------ void MainWindow::on_workspaceTreeView_doubleClicked(const QModelIndex &index) { - QString target = getWorkspace().getDirModel().data(index, REPODIRMODEL_ROLE_PATH).toString(); - target = getCurrentWorkspace() + PATH_SEPARATOR + target; + QVariant data = index.model()->data(index, ROLE_WORKSPACE_ITEM); + Q_ASSERT(data.isValid()); + WorkspaceItem tv = data.value(); + + if(tv.Type!=WorkspaceItem::TYPE_FOLDER && tv.Type!=WorkspaceItem::TYPE_WORKSPACE) + return; + + QString target = getCurrentWorkspace() + PATH_SEPARATOR + tv.Value; QUrl url = QUrl::fromLocalFile(target); QDesktopServices::openUrl(url); } @@ -1726,28 +1966,32 @@ { return NULL; } //------------------------------------------------------------------------------ -void MainWindow::on_actionViewStash_triggered() +const QIcon &MainWindow::getInternalIcon(const char* name) { - ui->stashTableView->setVisible(ui->actionViewStash->isChecked()); + if(iconCache.contains(name)) + return iconCache[name]; + + iconCache.insert(name, QIcon(name)); + return iconCache[name]; } //------------------------------------------------------------------------------ -void MainWindow::on_actionNewStash_triggered() +void MainWindow::on_actionCreateStash_triggered() { QStringList stashed_files; getSelectionFilenames(stashed_files, WorkspaceFile::TYPE_MODIFIED, true); if(stashed_files.empty()) return; QString stash_name; bool revert = false; - QString checkbox_text = tr("Revert stashed files"); - if(!CommitDialog::run(this, tr("Stash Changes"), stashed_files, stash_name, 0, true, &checkbox_text, &revert) || stashed_files.empty()) + + if(!CommitDialog::runStashNew(this, stashed_files, stash_name, revert) || stashed_files.empty()) return; stash_name = stash_name.trimmed(); if(stash_name.indexOf("\"")!=-1 || stash_name.isEmpty()) @@ -1774,11 +2018,14 @@ //------------------------------------------------------------------------------ void MainWindow::on_actionApplyStash_triggered() { QStringList stashes; - getStashViewSelection(stashes); + getSelectionStashes(stashes); + + if(stashes.empty()) + return; bool delete_stashes = false; if(!FileActionDialog::run(this, tr("Apply Stash"), tr("The following stashes will be applied.")+"\n"+tr("Are you sure?"), stashes, tr("Delete after applying"), &delete_stashes)) return; @@ -1813,11 +2060,11 @@ //------------------------------------------------------------------------------ void MainWindow::on_actionDeleteStash_triggered() { QStringList stashes; - getStashViewSelection(stashes); + getSelectionStashes(stashes); if(stashes.empty()) return; if(!FileActionDialog::run(this, tr("Delete Stashes"), tr("The following stashes will be deleted.")+"\n"+tr("Are you sure?"), stashes)) @@ -1841,11 +2088,11 @@ //------------------------------------------------------------------------------ void MainWindow::on_actionDiffStash_triggered() { QStringList stashes; - getStashViewSelection(stashes); + getSelectionStashes(stashes); if(stashes.length() != 1) return; stashmap_t::iterator id_it = getWorkspace().getStashes().find(*stashes.begin()); @@ -1886,21 +2133,21 @@ } //------------------------------------------------------------------------------ void MainWindow::on_fileTableView_customContextMenuRequested(const QPoint &pos) { - QPoint gpos = QCursor::pos(); + QPoint gpos = QCursor::pos() + QPoint(1, 1); #ifdef Q_OS_WIN if(qApp->keyboardModifiers() & Qt::SHIFT) { ui->fileTableView->selectionModel()->select(ui->fileTableView->indexAt(pos), QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Rows); QStringList fnames; getSelectionFilenames(fnames); if(fnames.size()==1) { - QString fname = getCurrentWorkspace() + PATH_SEP + fnames[0]; + QString fname = getCurrentWorkspace() + PATH_SEPARATOR + fnames[0]; fname = QDir::toNativeSeparators(fname); if(ShowExplorerMenu((HWND)winId(), fname, gpos)) refresh(); } } @@ -1913,10 +2160,42 @@ menu->addActions(ui->fileTableView->actions()); menu->popup(gpos); } } + +//------------------------------------------------------------------------------ +void MainWindow::on_workspaceTreeView_customContextMenuRequested(const QPoint &) +{ + QModelIndexList indices = ui->workspaceTreeView->selectionModel()->selectedIndexes(); + + if(indices.empty()) + return; + + QMenu *menu = 0; + + // Get first selected item + const QModelIndex &mi = indices.first(); + QVariant data = getWorkspace().getTreeModel().data(mi, ROLE_WORKSPACE_ITEM); + Q_ASSERT(data.isValid()); + WorkspaceItem tv = data.value(); + + if(tv.Type == WorkspaceItem::TYPE_FOLDER || tv.Type == WorkspaceItem::TYPE_WORKSPACE) + menu = menuWorkspace; + else if (tv.Type == WorkspaceItem::TYPE_STASH || tv.Type == WorkspaceItem::TYPE_STASHES) + menu = menuStashes; + else if (tv.Type == WorkspaceItem::TYPE_TAG || tv.Type == WorkspaceItem::TYPE_TAGS) + menu = menuTags; + else if (tv.Type == WorkspaceItem::TYPE_BRANCH || tv.Type == WorkspaceItem::TYPE_BRANCHES) + menu = menuBranches; + + if(menu) + { + QPoint pos = QCursor::pos() + QPoint(1, 1); + menu->popup(pos); + } +} //------------------------------------------------------------------------------ void MainWindow::dragEnterEvent(QDragEnterEvent *event) { // Ignore drops from the same window @@ -2014,10 +2293,12 @@ //------------------------------------------------------------------------------ void MainWindow::MainWinUICallback::beginProcess(const QString& text) { Q_ASSERT(mainWindow); mainWindow->ui->statusBar->showMessage(text); + mainWindow->lblTags->setHidden(true); + mainWindow->lblRevision->setHidden(true); mainWindow->progressBar->setHidden(false); QCoreApplication::processEvents(); } //------------------------------------------------------------------------------ @@ -2031,14 +2312,168 @@ //------------------------------------------------------------------------------ void MainWindow::MainWinUICallback::endProcess() { Q_ASSERT(mainWindow); mainWindow->ui->statusBar->clearMessage(); + mainWindow->lblTags->setHidden(false); + mainWindow->lblRevision->setHidden(false); mainWindow->progressBar->setHidden(true); QCoreApplication::processEvents(); } //------------------------------------------------------------------------------ QMessageBox::StandardButton MainWindow::MainWinUICallback::Query(const QString &title, const QString &query, QMessageBox::StandardButtons buttons) { return DialogQuery(mainWindow, title, query, buttons); } + +//------------------------------------------------------------------------------ +void MainWindow::updateRevision(const QString &revision) +{ + const QString latest = tr(REVISION_LATEST); + QString defaultval = latest; + + if(!revision.isEmpty()) + defaultval = revision; + + QString selected_revision = RevisionDialog::runUpdate(this, tr("Update workspace"), versionList, defaultval).trimmed(); + + if(selected_revision.isEmpty()) + return; + else if(selected_revision == latest) + selected_revision = ""; // Empty revision is "latest" + + QStringList res; + + // Do test update + if(!fossil().updateRepository(res, selected_revision, true)) + return; + + if(res.length()==0) + return; + + QStringMap kv; + ParseProperties(kv, res, ':'); + // If no changes exit + if(kv.contains("changes") && kv["changes"].indexOf("None.")!=-1) + return; + + if(!FileActionDialog::run(this, tr("Update"), tr("The following files will be updated.")+"\n"+tr("Are you sure?"), res)) + return; + + // Do update + fossil().updateRepository(res, selected_revision, false); + refresh(); +} + +//------------------------------------------------------------------------------ +void MainWindow::on_actionCreateTag_triggered() +{ + // Default to current revision + QString revision = fossil().getCurrentRevision(); + + QString name; + if(!RevisionDialog::runNewTag(this, tr("Create Tag"), versionList, revision, revision, name)) + return; + + if(name.isEmpty() || getWorkspace().getTags().contains(name) || getWorkspace().getBranches().contains(name)) + { + QMessageBox::critical(this, tr("Error"), tr("Invalid name."), QMessageBox::Ok ); + return; + } + + fossil().tagNew(name, revision); + refresh(); +} + +//------------------------------------------------------------------------------ +void MainWindow::on_actionDeleteTag_triggered() +{ + if(selectedTags.size()!=1) + return; + + const QString &tagname = selectedTags.first(); + + if(QMessageBox::Yes != DialogQuery(this, tr("Delete Tag"), tr("Are you sure want to delete the tag '%0' ?").arg(tagname))) + return; + + Q_ASSERT(getWorkspace().getTags().contains(tagname)); + + const QString &revision = getWorkspace().getTags()[tagname]; + + fossil().tagDelete(tagname, revision); + refresh(); +} + +//------------------------------------------------------------------------------ +void MainWindow::on_actionCreateBranch_triggered() +{ + // Default to current revision + QString revision = fossil().getCurrentRevision(); + + QString branch_name; + if(!RevisionDialog::runNewTag(this, tr("Create Branch"), versionList, revision, revision, branch_name)) + return; + + if(branch_name.isEmpty() || getWorkspace().getTags().contains(branch_name) || getWorkspace().getBranches().contains(branch_name)) + { + QMessageBox::critical(this, tr("Error"), tr("Invalid name."), QMessageBox::Ok ); + return; + } + + if(!fossil().branchNew(branch_name, revision, false)) + return; + + // Update to this branch. + updateRevision(branch_name); +} +//------------------------------------------------------------------------------ +void MainWindow::MergeRevision(const QString &defaultRevision) +{ + QStringList res; + QString revision = defaultRevision; + + bool integrate = false; + bool force = false; + revision = RevisionDialog::runMerge(this, tr("Merge Branch"), versionList, revision, integrate, force); + + if(revision.isEmpty()) + return; + + // Do test merge + if(!fossil().branchMerge(res, revision, integrate, force, true)) + return; + + if(!FileActionDialog::run(this, tr("Merge"), tr("The following changes will be applied.")+"\n"+tr("Are you sure?"), res)) + return; + + // Do update + fossil().branchMerge(res, revision, integrate, force, false); + + log(tr("Merge completed. Don't forget to commit!")+"\n"); + + refresh(); +} + +//------------------------------------------------------------------------------ +void MainWindow::on_actionMergeBranch_triggered() +{ + QString revision; + + if(!selectedBranches.isEmpty()) + revision = selectedBranches.first(); + MergeRevision(revision); +} + +//------------------------------------------------------------------------------ +void MainWindow::onSearchBoxTextChanged(const QString &) +{ + updateFileView(); +} + +//------------------------------------------------------------------------------ +void MainWindow::onSearch() +{ + searchBox->selectAll(); + searchBox->setFocus(); +} + Index: src/MainWindow.h ================================================================== --- src/MainWindow.h +++ src/MainWindow.h @@ -1,11 +1,12 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include -#include "SettingsDialog.h" +#include +#include "Settings.h" #include "Workspace.h" namespace Ui { class MainWindow; } @@ -26,38 +27,40 @@ private: bool refresh(); void scanWorkspace(); void applySettings(); void updateSettings(); + void updateRevision(const QString& revision); const QString &getCurrentWorkspace(); void setCurrentWorkspace(const QString &workspace); void log(const QString &text, bool isHTML=false); void setStatus(const QString &text); bool uiRunning() const; void getSelectionFilenames(QStringList &filenames, int includeMask=WorkspaceFile::TYPE_ALL, bool allIfEmpty=false); void getFileViewSelection(QStringList &filenames, int includeMask=WorkspaceFile::TYPE_ALL, bool allIfEmpty=false); void getDirViewSelection(QStringList &filenames, int includeMask=WorkspaceFile::TYPE_ALL, bool allIfEmpty=false); - void getStashViewSelection(QStringList &stashNames, bool allIfEmpty=false); + void getSelectionStashes(QStringList &stashNames); void getSelectionPaths(stringset_t &paths); void getAllFilenames(QStringList &filenames, int includeMask=WorkspaceFile::TYPE_ALL); bool startUI(); void stopUI(); void enableActions(bool on); void addWorkspace(const QString &dir); void rebuildRecent(); bool openWorkspace(const QString &path); void loadFossilSettings(); - QString getFossilHttpAddress(); - void updateDirView(); + void updateWorkspaceView(); void updateFileView(); - void updateStashView(); void selectRootDir(); + void MergeRevision(const QString& defaultRevision); + void fossilBrowse(const QString &fossilUrl); void dragEnterEvent(class QDragEnterEvent *event); void dropEvent(class QDropEvent *event); void setBusy(bool busy); virtual QMenu *createPopupMenu(); + const QIcon& getInternalIcon(const char *name); enum ViewMode { VIEWMODE_LIST, VIEWMODE_TREE @@ -68,10 +71,12 @@ // Use a different naming scheme to prevent warnings from Qt's automatic signaling void onOpenRecent(); void onWorkspaceTreeViewSelectionChanged(const class QItemSelection &selected, const class QItemSelection &deselected); void onFileViewDragOut(); void onAbort(); + void onSearchBoxTextChanged(const QString &text); + void onSearch(); // Designer slots void on_actionRefresh_triggered(); void on_actionDiff_triggered(); void on_actionFossilUI_triggered(); @@ -92,28 +97,35 @@ void on_actionRename_triggered(); void on_actionUndo_triggered(); void on_actionAbout_triggered(); void on_actionUpdate_triggered(); void on_actionSettings_triggered(); + void on_actionFossilSettings_triggered(); void on_actionViewUnchanged_triggered(); void on_actionViewModified_triggered(); void on_actionViewUnknown_triggered(); void on_actionViewIgnored_triggered(); + void on_actionViewAll_triggered(); void on_actionViewAsList_triggered(); + void on_actionViewAsFolders_triggered(); void on_actionOpenFolder_triggered(); void on_actionRenameFolder_triggered(); void on_actionNewRepository_triggered(); void on_actionOpenRepository_triggered(); void on_actionCloseRepository_triggered(); void on_actionCloneRepository_triggered(); - void on_actionViewStash_triggered(); - void on_actionNewStash_triggered(); + void on_actionCreateStash_triggered(); void on_actionApplyStash_triggered(); void on_actionDeleteStash_triggered(); void on_actionDiffStash_triggered(); void on_textBrowser_customContextMenuRequested(const QPoint &pos); void on_fileTableView_customContextMenuRequested(const QPoint &pos); + void on_workspaceTreeView_customContextMenuRequested(const QPoint &pos); + void on_actionCreateTag_triggered(); + void on_actionDeleteTag_triggered(); + void on_actionCreateBranch_triggered(); + void on_actionMergeBranch_triggered(); private: class MainWinUICallback : public UICallback { public: @@ -141,16 +153,32 @@ enum { MAX_RECENT=5 }; + typedef QMap icon_map_t; + Ui::MainWindow *ui; + QFileIconProvider iconProvider; + icon_map_t iconCache; class QAction *recentWorkspaceActs[MAX_RECENT]; class QProgressBar *progressBar; + class QLabel *lblRevision; + class QLabel *lblTags; class QShortcut *abortShortcut; + class SearchBox *searchBox; + class QShortcut *searchShortcut; + QMenu *menuWorkspace; + QMenu *menuStashes; + QMenu *menuTags; + QMenu *menuBranches; + bool operationAborted; stringset_t selectedDirs; // The directory selected in the tree + QStringList selectedTags; + QStringList selectedBranches; + QStringList versionList; Workspace workspace; Workspace & getWorkspace() { return workspace; } Fossil & fossil() { return workspace.fossil(); } ADDED src/RevisionDialog.cpp Index: src/RevisionDialog.cpp ================================================================== --- /dev/null +++ src/RevisionDialog.cpp @@ -0,0 +1,92 @@ +#include "RevisionDialog.h" +#include "ui_RevisionDialog.h" +#include "Utils.h" + +//----------------------------------------------------------------------------- +RevisionDialog::RevisionDialog(QWidget *parent, const QStringList &completions, const QString &defaultValue) : + QDialog(parent), + ui(new Ui::RevisionDialog), + completer(completions, parent) +{ + ui->setupUi(this); + ui->cmbRevision->setCompleter(&completer); + + ui->cmbRevision->addItems(completions); + + if(!defaultValue.isEmpty()) + ui->cmbRevision->setCurrentText(defaultValue); +} + +//----------------------------------------------------------------------------- +RevisionDialog::~RevisionDialog() +{ + delete ui; +} + +//----------------------------------------------------------------------------- +QString RevisionDialog::runUpdate(QWidget *parent, const QString &title, const QStringList &completions, const QString &defaultValue) +{ + RevisionDialog dlg(parent, completions, defaultValue); + dlg.setWindowTitle(title); + dlg.ui->lblName->setVisible(false); + dlg.ui->lineName->setVisible(false); + dlg.ui->lblIntegrate->setVisible(false); + dlg.ui->chkIntegrate->setVisible(false); + dlg.ui->lblForce->setVisible(false); + dlg.ui->chkForce->setVisible(false); + + dlg.adjustSize(); + + if(dlg.exec() != QDialog::Accepted) + return QString(""); + return dlg.ui->cmbRevision->currentText().trimmed(); +} + +//----------------------------------------------------------------------------- +QString RevisionDialog::runMerge(QWidget *parent, const QString &title, const QStringList &completions, const QString &defaultValue, bool &integrate, bool &force) +{ + RevisionDialog dlg(parent, completions, defaultValue); + dlg.setWindowTitle(title); + dlg.ui->lblName->setVisible(false); + dlg.ui->lineName->setVisible(false); + dlg.ui->lblIntegrate->setVisible(true); + dlg.ui->chkIntegrate->setVisible(true); + dlg.ui->chkIntegrate->setChecked(integrate); + dlg.ui->lblForce->setVisible(true); + dlg.ui->chkForce->setVisible(true); + dlg.ui->chkForce->setChecked(force); + + dlg.adjustSize(); + + if(dlg.exec() != QDialog::Accepted) + return QString(""); + + integrate = dlg.ui->chkIntegrate->checkState() == Qt::Checked; + force = dlg.ui->chkForce->checkState() == Qt::Checked; + + return dlg.ui->cmbRevision->currentText().trimmed(); +} + + +//----------------------------------------------------------------------------- +bool RevisionDialog::runNewTag(QWidget *parent, const QString &title, const QStringList &completions, const QString &defaultValue, QString &revision, QString &name) +{ + RevisionDialog dlg(parent, completions, defaultValue); + dlg.setWindowTitle(title); + + dlg.ui->lblName->setVisible(true); + dlg.ui->lineName->setVisible(true); + dlg.ui->lblIntegrate->setVisible(false); + dlg.ui->chkIntegrate->setVisible(false); + dlg.ui->lblForce->setVisible(false); + dlg.ui->chkForce->setVisible(false); + + dlg.adjustSize(); + + if(dlg.exec() != QDialog::Accepted) + return false; + + revision = dlg.ui->cmbRevision->currentText().trimmed(); + name = dlg.ui->lineName->text().trimmed(); + return true; +} ADDED src/RevisionDialog.h Index: src/RevisionDialog.h ================================================================== --- /dev/null +++ src/RevisionDialog.h @@ -0,0 +1,28 @@ +#ifndef REVISIONDIALOG_H +#define REVISIONDIALOG_H + +#include +#include + +namespace Ui { + class RevisionDialog; +} + +class RevisionDialog : public QDialog +{ + Q_OBJECT + +public: + explicit RevisionDialog(QWidget *parent, const QStringList &completions, const QString &defaultValue); + ~RevisionDialog(); + + static QString runUpdate(QWidget *parent, const QString &title, const QStringList &completions, const QString &defaultValue); + static QString runMerge(QWidget* parent, const QString& title, const QStringList& completions, const QString& defaultValue, bool& integrate, bool& force); + static bool runNewTag(QWidget *parent, const QString &title, const QStringList &completions, const QString &defaultValue, QString &revision, QString &name); + +private: + Ui::RevisionDialog *ui; + QCompleter completer; +}; + +#endif // REVISIONDIALOG_H ADDED src/SearchBox.cpp Index: src/SearchBox.cpp ================================================================== --- /dev/null +++ src/SearchBox.cpp @@ -0,0 +1,24 @@ +#include "SearchBox.h" +#include + +SearchBox::SearchBox(QWidget *parent) : QLineEdit(parent) +{ +} + +SearchBox::~SearchBox() +{ + +} + +void SearchBox::keyPressEvent(QKeyEvent *event) +{ + // Clear text on escape + if(event->key() == Qt::Key_Escape) + { + setText(""); + clearFocus(); + } + else + QLineEdit::keyPressEvent(event); +} + ADDED src/SearchBox.h Index: src/SearchBox.h ================================================================== --- /dev/null +++ src/SearchBox.h @@ -0,0 +1,23 @@ +#ifndef SEARCHBOX_H +#define SEARCHBOX_H + +#include + +class SearchBox : public QLineEdit +{ + Q_OBJECT +public: + explicit SearchBox(QWidget* parent=0); + ~SearchBox(); + +signals: + +public slots: + +protected: + void keyPressEvent(QKeyEvent *event); + +}; + + +#endif // SEARCHBOX_H ADDED src/Settings.cpp Index: src/Settings.cpp ================================================================== --- /dev/null +++ src/Settings.cpp @@ -0,0 +1,124 @@ +#include "Settings.h" + +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +Settings::Settings(bool portableMode) : store(0) +{ + Mappings.insert(FOSSIL_SETTING_GDIFF_CMD, Setting("", Setting::TYPE_FOSSIL_GLOBAL)); + Mappings.insert(FOSSIL_SETTING_GMERGE_CMD, Setting("", Setting::TYPE_FOSSIL_GLOBAL)); + Mappings.insert(FOSSIL_SETTING_PROXY_URL, Setting("", Setting::TYPE_FOSSIL_GLOBAL)); + Mappings.insert(FOSSIL_SETTING_HTTP_PORT, Setting("", Setting::TYPE_FOSSIL_GLOBAL)); + + Mappings.insert(FOSSIL_SETTING_IGNORE_GLOB, Setting("", Setting::TYPE_FOSSIL_LOCAL)); + Mappings.insert(FOSSIL_SETTING_CRNL_GLOB, Setting("", Setting::TYPE_FOSSIL_LOCAL)); + Mappings.insert(FOSSIL_SETTING_REMOTE_URL, Setting("off", Setting::TYPE_FOSSIL_COMMAND)); + + // Go into portable mode when explicitly requested or if a config file exists next to the executable + QString ini_path = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + QDir::separator() + QCoreApplication::applicationName() + ".ini"); + if( portableMode || QFile::exists(ini_path)) + store = new QSettings(ini_path, QSettings::IniFormat); + else + { + // Linux: ~/.config/organizationName/applicationName.conf + // Windows: HKEY_CURRENT_USER\Software\organizationName\Fuel + store = new QSettings(QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); + } + Q_ASSERT(store); + + if(!HasValue(FUEL_SETTING_FILE_DBLCLICK)) + SetValue(FUEL_SETTING_FILE_DBLCLICK, 0); + if(!HasValue(FUEL_SETTING_LANGUAGE) && SupportsLang(QLocale::system().name())) + SetValue(FUEL_SETTING_LANGUAGE, QLocale::system().name()); + if(!HasValue(FUEL_SETTING_WEB_BROWSER)) + SetValue(FUEL_SETTING_WEB_BROWSER, 0); + + ApplyEnvironment(); +} + +//----------------------------------------------------------------------------- +Settings::~Settings() +{ + Q_ASSERT(store); + delete store; +} + +//----------------------------------------------------------------------------- +void Settings::ApplyEnvironment() +{ + QString lang_id = GetValue(FUEL_SETTING_LANGUAGE).toString(); +#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) + QTextCodec::setCodecForTr(QTextCodec::codecForName("utf8")); +#endif + if(!InstallLang(lang_id)) + SetValue(FUEL_SETTING_LANGUAGE, "en_US"); +} + +//----------------------------------------------------------------------------- +bool Settings::InstallLang(const QString &langId) +{ + if(langId == "en_US") + { + QCoreApplication::instance()->removeTranslator(&translator); + return true; + } + + QString locale_path = QString(":intl/intl/%0.qm").arg(langId); + if(!translator.load(locale_path)) + return false; + + Q_ASSERT(!translator.isEmpty()); + QCoreApplication::instance()->installTranslator(&translator); + + + return true; +} + +//----------------------------------------------------------------------------- +bool Settings::HasValue(const QString &name) const +{ + return store->contains(name); +} + +//----------------------------------------------------------------------------- +const QVariant Settings::GetValue(const QString &name) +{ + if(!HasValue(name)) + return QVariant(); + return store->value(name); +} + +//----------------------------------------------------------------------------- +void Settings::SetValue(const QString &name, const QVariant &value) +{ + store->setValue(name, value); +} + +//----------------------------------------------------------------------------- +QVariant &Settings::GetFossilValue(const QString &name) +{ + mappings_t::iterator it=Mappings.find(name); + Q_ASSERT(it!=Mappings.end()); + return it.value().Value; +} + +//----------------------------------------------------------------------------- +void Settings::SetFossilValue(const QString &name, const QVariant &value) +{ + mappings_t::iterator it=Mappings.find(name); + Q_ASSERT(it!=Mappings.end()); + it->Value = value; +} + +//----------------------------------------------------------------------------- +bool Settings::SupportsLang(const QString &langId) const +{ + QString locale_path = QString(":intl/intl/%0.qm").arg(langId); + QResource res(locale_path); + return res.isValid(); +} ADDED src/Settings.h Index: src/Settings.h ================================================================== --- /dev/null +++ src/Settings.h @@ -0,0 +1,75 @@ +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include +#include + +#define FUEL_SETTING_FOSSIL_PATH "FossilPath" +#define FUEL_SETTING_COMMIT_MSG "CommitMsgHistory" +#define FUEL_SETTING_FILE_DBLCLICK "FileDblClickAction" +#define FUEL_SETTING_LANGUAGE "Language" +#define FUEL_SETTING_WEB_BROWSER "WebBrowser" + +#define FOSSIL_SETTING_GDIFF_CMD "gdiff-command" +#define FOSSIL_SETTING_GMERGE_CMD "gmerge-command" +#define FOSSIL_SETTING_PROXY_URL "proxy" +#define FOSSIL_SETTING_IGNORE_GLOB "ignore-glob" +#define FOSSIL_SETTING_CRNL_GLOB "crnl-glob" +#define FOSSIL_SETTING_REMOTE_URL "remote-url" +#define FOSSIL_SETTING_HTTP_PORT "http-port" + + +enum FileDblClickAction +{ + FILE_DLBCLICK_ACTION_DIFF, + FILE_DLBCLICK_ACTION_OPEN, + FILE_DLBCLICK_ACTION_OPENCONTAINING, + FILE_DLBCLICK_ACTION_MAX +}; + +struct Settings +{ + struct Setting + { + enum SettingType + { + TYPE_FOSSIL_GLOBAL, + TYPE_FOSSIL_LOCAL, + TYPE_FOSSIL_COMMAND + }; + + Setting(QVariant value, SettingType type) : Value(value), Type(type) + {} + QVariant Value; + SettingType Type; + }; + typedef QMap mappings_t; + + + Settings(bool portableMode = false); + ~Settings(); + + void ApplyEnvironment(); + + // App configuration access + class QSettings * GetStore() { return store; } + bool HasValue(const QString &name) const; // store->contains(FUEL_SETTING_FOSSIL_PATH) + const QVariant GetValue(const QString &name); // settings.store->value + void SetValue(const QString &name, const QVariant &value); // settings.store->value + + // Fossil configuration access + QVariant & GetFossilValue(const QString &name); + void SetFossilValue(const QString &name, const QVariant &value); + mappings_t & GetMappings() { return Mappings; } + + bool SupportsLang(const QString &langId) const; + bool InstallLang(const QString &langId); +private: + mappings_t Mappings; + class QSettings *store; + QTranslator translator; +}; + + +#endif // SETTINGS_H Index: src/SettingsDialog.cpp ================================================================== --- src/SettingsDialog.cpp +++ src/SettingsDialog.cpp @@ -1,39 +1,10 @@ #include "SettingsDialog.h" #include "ui_SettingsDialog.h" -#include #include "Utils.h" -#include -#include #include -#include -#include -#include - - -/////////////////////////////////////////////////////////////////////////////// -QString SettingsDialog::SelectExe(QWidget *parent, const QString &description) -{ - QString filter(tr("Applications")); -#ifdef Q_OS_WIN - filter += " (*.exe)"; -#else - filter += " (*)"; -#endif - QString path = QFileDialog::getOpenFileName( - parent, - description, - QString(), - filter, - &filter); - - if(!QFile::exists(path)) - return QString(); - - return path; -} /////////////////////////////////////////////////////////////////////////////// SettingsDialog::SettingsDialog(QWidget *parent, Settings &_settings) : QDialog(parent, Qt::Sheet), ui(new Ui::SettingsDialog), @@ -52,11 +23,10 @@ // App Settings ui->lineFossilPath->setText(QDir::toNativeSeparators(settings->GetValue(FUEL_SETTING_FOSSIL_PATH).toString())); ui->cmbDoubleClickAction->setCurrentIndex(settings->GetValue(FUEL_SETTING_FILE_DBLCLICK).toInt()); ui->cmbFossilBrowser->setCurrentIndex(settings->GetValue(FUEL_SETTING_WEB_BROWSER).toInt()); - ui->lineUIPort->setText(settings->GetValue(FUEL_SETTING_HTTP_PORT).toString()); // Initialize language combo foreach(const LangMap &m, langMap) ui->cmbActiveLanguage->addItem(m.name); @@ -64,19 +34,10 @@ // Select current language ui->cmbActiveLanguage->setCurrentIndex( ui->cmbActiveLanguage->findText( LangIdToName(lang))); - // Global Settings - ui->lineGDiffCommand->setText(settings->GetFossilValue(FOSSIL_SETTING_GDIFF_CMD).toString()); - ui->lineGMergeCommand->setText(settings->GetFossilValue(FOSSIL_SETTING_GMERGE_CMD).toString()); - ui->lineProxy->setText(settings->GetFossilValue(FOSSIL_SETTING_PROXY_URL).toString()); - - // Repository Settings - ui->lineRemoteURL->setText(settings->GetFossilValue(FOSSIL_SETTING_REMOTE_URL).toString()); - ui->lineIgnore->setText(settings->GetFossilValue(FOSSIL_SETTING_IGNORE_GLOB).toString()); - ui->lineIgnoreCRNL->setText(settings->GetFossilValue(FOSSIL_SETTING_CRNL_GLOB).toString()); } //----------------------------------------------------------------------------- SettingsDialog::~SettingsDialog() { @@ -95,29 +56,19 @@ { settings->SetValue(FUEL_SETTING_FOSSIL_PATH, QDir::fromNativeSeparators(ui->lineFossilPath->text())); Q_ASSERT(ui->cmbDoubleClickAction->currentIndex()>=FILE_DLBCLICK_ACTION_DIFF && ui->cmbDoubleClickAction->currentIndex()SetValue(FUEL_SETTING_FILE_DBLCLICK, ui->cmbDoubleClickAction->currentIndex()); settings->SetValue(FUEL_SETTING_WEB_BROWSER, ui->cmbFossilBrowser->currentIndex()); - settings->SetValue(FUEL_SETTING_HTTP_PORT, ui->lineUIPort->text()); Q_ASSERT(settings->HasValue(FUEL_SETTING_LANGUAGE)); QString curr_langid = settings->GetValue(FUEL_SETTING_LANGUAGE).toString(); QString new_langid = LangNameToId(ui->cmbActiveLanguage->currentText()); Q_ASSERT(!new_langid.isEmpty()); settings->SetValue(FUEL_SETTING_LANGUAGE, new_langid); if(curr_langid != new_langid) QMessageBox::information(this, tr("Restart required"), tr("The language change will take effect after restarting the application"), QMessageBox::Ok); - - settings->SetFossilValue(FOSSIL_SETTING_GDIFF_CMD, ui->lineGDiffCommand->text()); - settings->SetFossilValue(FOSSIL_SETTING_GMERGE_CMD, ui->lineGMergeCommand->text()); - settings->SetFossilValue(FOSSIL_SETTING_PROXY_URL, ui->lineProxy->text()); - - settings->SetFossilValue(FOSSIL_SETTING_REMOTE_URL, ui->lineRemoteURL->text()); - settings->SetFossilValue(FOSSIL_SETTING_IGNORE_GLOB, ui->lineIgnore->text()); - settings->SetFossilValue(FOSSIL_SETTING_CRNL_GLOB, ui->lineIgnoreCRNL->text()); - settings->ApplyEnvironment(); } //----------------------------------------------------------------------------- @@ -126,26 +77,10 @@ QString path = SelectExe(this, tr("Select Fossil executable")); if(!path.isEmpty()) ui->lineFossilPath->setText(QDir::toNativeSeparators(path)); } -//----------------------------------------------------------------------------- -void SettingsDialog::on_btnSelectFossilGDiff_clicked() -{ - QString path = SelectExe(this, tr("Select Graphical Diff application")); - if(!path.isEmpty()) - ui->lineGDiffCommand->setText(QDir::toNativeSeparators(path)); -} - -//----------------------------------------------------------------------------- -void SettingsDialog::on_btnSelectGMerge_clicked() -{ - QString path = SelectExe(this, tr("Select Graphical Merge application")); - if(!path.isEmpty()) - ui->lineGMergeCommand->setText(path); -} - //----------------------------------------------------------------------------- void SettingsDialog::on_btnClearMessageHistory_clicked() { if(DialogQuery(this, tr("Clear Commit Message History"), tr("Are you sure you want to clear the commit message history?"))==QMessageBox::Yes) settings->SetValue(FUEL_SETTING_COMMIT_MSG, QStringList()); @@ -184,122 +119,5 @@ return m.id; } return ""; } - -/////////////////////////////////////////////////////////////////////////////// -Settings::Settings(bool portableMode) : store(0) -{ - Mappings.insert(FOSSIL_SETTING_GDIFF_CMD, Setting("", Setting::TYPE_FOSSIL_GLOBAL)); - Mappings.insert(FOSSIL_SETTING_GMERGE_CMD, Setting("", Setting::TYPE_FOSSIL_GLOBAL)); - Mappings.insert(FOSSIL_SETTING_PROXY_URL, Setting("", Setting::TYPE_FOSSIL_GLOBAL)); - - Mappings.insert(FOSSIL_SETTING_IGNORE_GLOB, Setting("", Setting::TYPE_FOSSIL_LOCAL)); - Mappings.insert(FOSSIL_SETTING_CRNL_GLOB, Setting("", Setting::TYPE_FOSSIL_LOCAL)); - Mappings.insert(FOSSIL_SETTING_REMOTE_URL, Setting("off", Setting::TYPE_FOSSIL_COMMAND)); - - // Go into portable mode when explicitly requested or if a config file exists next to the executable - QString ini_path = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + QDir::separator() + QCoreApplication::applicationName() + ".ini"); - if( portableMode || QFile::exists(ini_path)) - store = new QSettings(ini_path, QSettings::IniFormat); - else - { - // Linux: ~/.config/organizationName/applicationName.conf - // Windows: HKEY_CURRENT_USER\Software\organizationName\Fuel - store = new QSettings(QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); - } - Q_ASSERT(store); - - if(!HasValue(FUEL_SETTING_FILE_DBLCLICK)) - SetValue(FUEL_SETTING_FILE_DBLCLICK, 0); - if(!HasValue(FUEL_SETTING_LANGUAGE) && SupportsLang(QLocale::system().name())) - SetValue(FUEL_SETTING_LANGUAGE, QLocale::system().name()); - if(!HasValue(FUEL_SETTING_WEB_BROWSER)) - SetValue(FUEL_SETTING_WEB_BROWSER, 0); - if(!HasValue(FUEL_SETTING_HTTP_PORT)) - SetValue(FUEL_SETTING_HTTP_PORT, "8090"); - - ApplyEnvironment(); -} - -//----------------------------------------------------------------------------- -Settings::~Settings() -{ - Q_ASSERT(store); - delete store; -} - -//----------------------------------------------------------------------------- -void Settings::ApplyEnvironment() -{ - QString lang_id = GetValue(FUEL_SETTING_LANGUAGE).toString(); -#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) - QTextCodec::setCodecForTr(QTextCodec::codecForName("utf8")); -#endif - if(!InstallLang(lang_id)) - SetValue(FUEL_SETTING_LANGUAGE, "en_US"); -} - -//----------------------------------------------------------------------------- -bool Settings::InstallLang(const QString &langId) -{ - if(langId == "en_US") - { - QCoreApplication::instance()->removeTranslator(&translator); - return true; - } - - QString locale_path = QString(":intl/intl/%0.qm").arg(langId); - if(!translator.load(locale_path)) - return false; - - Q_ASSERT(!translator.isEmpty()); - QCoreApplication::instance()->installTranslator(&translator); - - - return true; -} - -//----------------------------------------------------------------------------- -bool Settings::HasValue(const QString &name) const -{ - return store->contains(name); -} - -//----------------------------------------------------------------------------- -const QVariant Settings::GetValue(const QString &name) -{ - if(!HasValue(name)) - return QVariant(); - return store->value(name); -} - -//----------------------------------------------------------------------------- -void Settings::SetValue(const QString &name, const QVariant &value) -{ - store->setValue(name, value); -} - -//----------------------------------------------------------------------------- -QVariant &Settings::GetFossilValue(const QString &name) -{ - mappings_t::iterator it=Mappings.find(name); - Q_ASSERT(it!=Mappings.end()); - return it.value().Value; -} - -//----------------------------------------------------------------------------- -void Settings::SetFossilValue(const QString &name, const QVariant &value) -{ - mappings_t::iterator it=Mappings.find(name); - Q_ASSERT(it!=Mappings.end()); - it->Value = value; -} - -//----------------------------------------------------------------------------- -bool Settings::SupportsLang(const QString &langId) const -{ - QString locale_path = QString(":intl/intl/%0.qm").arg(langId); - QResource res(locale_path); - return res.isValid(); -} Index: src/SettingsDialog.h ================================================================== --- src/SettingsDialog.h +++ src/SettingsDialog.h @@ -1,84 +1,14 @@ #ifndef SETTINGSDIALOG_H #define SETTINGSDIALOG_H #include -#include -#include -#include - +#include "Settings.h" namespace Ui { class SettingsDialog; } - -#define FUEL_SETTING_FOSSIL_PATH "FossilPath" -#define FUEL_SETTING_COMMIT_MSG "CommitMsgHistory" -#define FUEL_SETTING_FILE_DBLCLICK "FileDblClickAction" -#define FUEL_SETTING_LANGUAGE "Language" -#define FUEL_SETTING_WEB_BROWSER "WebBrowser" -#define FUEL_SETTING_HTTP_PORT "HTTPPort" - -#define FOSSIL_SETTING_GDIFF_CMD "gdiff-command" -#define FOSSIL_SETTING_GMERGE_CMD "gmerge-command" -#define FOSSIL_SETTING_PROXY_URL "proxy" -#define FOSSIL_SETTING_IGNORE_GLOB "ignore-glob" -#define FOSSIL_SETTING_CRNL_GLOB "crnl-glob" -#define FOSSIL_SETTING_REMOTE_URL "remote-url" - - -enum FileDblClickAction -{ - FILE_DLBCLICK_ACTION_DIFF, - FILE_DLBCLICK_ACTION_OPEN, - FILE_DLBCLICK_ACTION_OPENCONTAINING, - FILE_DLBCLICK_ACTION_MAX -}; - -struct Settings -{ - struct Setting - { - enum SettingType - { - TYPE_FOSSIL_GLOBAL, - TYPE_FOSSIL_LOCAL, - TYPE_FOSSIL_COMMAND - }; - - Setting(QVariant value, SettingType type) : Value(value), Type(type) - {} - QVariant Value; - SettingType Type; - }; - typedef QMap mappings_t; - - - Settings(bool portableMode = false); - ~Settings(); - - void ApplyEnvironment(); - - // App configuration access - class QSettings * GetStore() { return store; } - bool HasValue(const QString &name) const; // store->contains(FUEL_SETTING_FOSSIL_PATH) - const QVariant GetValue(const QString &name); // settings.store->value - void SetValue(const QString &name, const QVariant &value); // settings.store->value - - // Fossil configuration access - QVariant & GetFossilValue(const QString &name); - void SetFossilValue(const QString &name, const QVariant &value); - mappings_t & GetMappings() { return Mappings; } - - bool SupportsLang(const QString &langId) const; - bool InstallLang(const QString &langId); -private: - mappings_t Mappings; - class QSettings *store; - QTranslator translator; -}; - class SettingsDialog : public QDialog { Q_OBJECT @@ -90,16 +20,13 @@ private slots: void on_btnSelectFossil_clicked(); void on_buttonBox_accepted(); - void on_btnSelectFossilGDiff_clicked(); - void on_btnSelectGMerge_clicked(); void on_btnClearMessageHistory_clicked(); private: - static QString SelectExe(QWidget *parent, const QString &description); QString LangIdToName(const QString &id); QString LangNameToId(const QString &name); void CreateLangMap(); struct LangMap Index: src/Utils.cpp ================================================================== --- src/Utils.cpp +++ src/Utils.cpp @@ -1,8 +1,9 @@ #include "Utils.h" #include #include +#include /////////////////////////////////////////////////////////////////////////////// QMessageBox::StandardButton DialogQuery(QWidget *parent, const QString &title, const QString &query, QMessageBox::StandardButtons buttons) { QMessageBox mb(QMessageBox::Question, title, query, buttons, parent, Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint | Qt::Sheet ); @@ -27,10 +28,31 @@ for(int i=0; i static bool DialogQueryText(QWidget *parent, const QString &title, const QString &query, QString &text, bool isPassword=false) @@ -304,5 +326,66 @@ return bResult; } #endif +//----------------------------------------------------------------------------- +void ParseProperties(QStringMap &properties, const QStringList &lines, QChar separator) +{ + properties.clear(); + foreach(QString l, lines) + { + l = l.trimmed(); + int index = l.indexOf(separator); + + QString key; + QString value; + if(index!=-1) + { + key = l.left(index).trimmed(); + value = l.mid(index+1).trimmed(); + } + else + key = l; + + properties.insert(key, value); + } +} + + +//------------------------------------------------------------------------------ +void GetStandardItemTextRecursive(QString &name, const QStandardItem &item, const QChar &separator) +{ + if(item.parent()) + { + GetStandardItemTextRecursive(name, *item.parent()); + name.append(separator); + } + + name.append(item.data(Qt::DisplayRole).toString()); +} + +//------------------------------------------------------------------------------ +void BuildNameToModelIndex(name_modelindex_map_t &map, const QStandardItem &item) +{ + QString name; + GetStandardItemTextRecursive(name, item); + map.insert(name, item.index()); + + for(int i=0; i #include +#include +#include #define COUNTOF(array) (sizeof(array)/sizeof(array[0])) #define FOSSIL_CHECKOUT1 "_FOSSIL_" #define FOSSIL_CHECKOUT2 ".fslckout" #define FOSSIL_EXT "fossil" @@ -11,10 +13,21 @@ QMessageBox::StandardButton DialogQuery(QWidget *parent, const QString &title, const QString &query, QMessageBox::StandardButtons buttons = QMessageBox::Yes|QMessageBox::No); QString QuotePath(const QString &path); QStringList QuotePaths(const QStringList &paths); +QString SelectExe(QWidget *parent, const QString &description); + + +typedef QMap name_modelindex_map_t; +void GetStandardItemTextRecursive(QString &name, const QStandardItem &item, const QChar &separator='/'); +void BuildNameToModelIndex(name_modelindex_map_t &map, const QStandardItem &item); +void BuildNameToModelIndex(name_modelindex_map_t &map, const QStandardItemModel &model); + + +typedef QMap QStringMap; +void ParseProperties(QStringMap &properties, const QStringList &lines, QChar separator=' '); #ifdef Q_OS_WIN bool ShowExplorerMenu(HWND hwnd, const QString &path, const QPoint &qpoint); #endif Index: src/Workspace.cpp ================================================================== --- src/Workspace.cpp +++ src/Workspace.cpp @@ -15,10 +15,14 @@ foreach(WorkspaceFile *r, getFiles()) delete r; getFiles().clear(); getPaths().clear(); + stashMap.clear(); + branchList.clear(); + tags.clear(); + isIntegrated = false; } //------------------------------------------------------------------------------ bool Workspace::scanDirectory(QFileInfoList &entries, const QString& dirPath, const QString &baseDir, const QString ignoreSpec, const bool &abort, UICallback &uiCallback) { @@ -114,12 +118,16 @@ { QString line = (*line_it).trimmed(); if(line.length()==0) continue; - QString status_text = line.left(10).trimmed(); - QString fname = line.right(line.length() - 10).trimmed(); + int space_index = line.indexOf(' '); + if(space_index==-1) + continue; + + QString status_text = line.left(space_index); + QString fname = line.right(line.length() - space_index).trimmed(); WorkspaceFile::Type type = WorkspaceFile::TYPE_UNKNOWN; // Generate a RepoFile for all non-existant fossil files // or for all files if we skipped scanning the workspace bool add_missing = !scan_files; @@ -142,10 +150,12 @@ type = WorkspaceFile::TYPE_RENAMED; else if(status_text=="UNCHANGED") type = WorkspaceFile::TYPE_UNCHANGED; else if(status_text=="CONFLICT") type = WorkspaceFile::TYPE_CONFLICTED; + else if(status_text=="UPDATED_BY_MERGE" || status_text=="ADDED_BY_MERGE" || status_text=="ADDED_BY_INTEGRATE" || status_text=="UPDATED_BY_INTEGRATE") + type = WorkspaceFile::TYPE_MERGED; // Filter unwanted file types if( ((type & WorkspaceFile::TYPE_MODIFIED) && !scanModified) || ((type & WorkspaceFile::TYPE_UNCHANGED) && !scanUnchanged)) { @@ -176,11 +186,33 @@ QString path = rf->getPath(); getPaths().insert(path); } - // Load the stash + // Check if the repository needs integration + res.clear(); + fossil().status(res); + isIntegrated = false; + foreach(const QString &l, res) + { + if(l.trimmed().indexOf("INTEGRATE")==0) + { + isIntegrated = true; + break; + } + } + + // Load the stashes, branches and tags fossil().stashList(getStashes()); + + fossil().branchList(branchList, branchList); + + fossil().tagList(tags); + // Fossil includes the branches in the tag list + // So remove them + foreach(const QString &name, branchList) + tags.remove(name); + _done: uiCallback.endProcess(); } Index: src/Workspace.h ================================================================== --- src/Workspace.h +++ src/Workspace.h @@ -4,10 +4,11 @@ #include #include #include #include #include +#include "Utils.h" #include "Fossil.h" ////////////////////////////////////////////////////////////////////////// // WorkspaceFile ////////////////////////////////////////////////////////////////////////// @@ -21,11 +22,12 @@ TYPE_ADDED = 1<<3, TYPE_DELETED = 1<<4, TYPE_MISSING = 1<<5, TYPE_RENAMED = 1<<6, TYPE_CONFLICTED = 1<<7, - TYPE_MODIFIED = TYPE_EDITTED|TYPE_ADDED|TYPE_DELETED|TYPE_MISSING|TYPE_RENAMED|TYPE_CONFLICTED, + TYPE_MERGED = 1<<8, + TYPE_MODIFIED = TYPE_EDITTED|TYPE_ADDED|TYPE_DELETED|TYPE_MISSING|TYPE_RENAMED|TYPE_CONFLICTED|TYPE_MERGED, TYPE_REPO = TYPE_UNCHANGED|TYPE_MODIFIED, TYPE_ALL = TYPE_UNKNOWN|TYPE_REPO }; WorkspaceFile(const QFileInfo &info, Type type, const QString &repoPath) @@ -95,10 +97,14 @@ }; typedef QSet stringset_t; +////////////////////////////////////////////////////////////////////////// +// Workspace +////////////////////////////////////////////////////////////////////////// + class Workspace { public: ~Workspace(); @@ -112,23 +118,28 @@ static bool scanDirectory(QFileInfoList &entries, const QString& dirPath, const QString &baseDir, const QString ignoreSpec, const bool& abort, UICallback &uiCallback); void scanWorkspace(bool scanLocal, bool scanIgnored, bool scanModified, bool scanUnchanged, const QString &ignoreGlob, UICallback &uiCallback, bool &operationAborted); QStandardItemModel &getFileModel() { return repoFileModel; } - QStandardItemModel &getDirModel() { return repoDirModel; } - QStandardItemModel &getStashModel() { return repoStashModel; } + QStandardItemModel &getTreeModel() { return repoTreeModel; } + filemap_t &getFiles() { return workspaceFiles; } stringset_t &getPaths() { return pathSet; } stashmap_t &getStashes() { return stashMap; } + QStringMap &getTags() { return tags; } + QStringList &getBranches() { return branchList; } + bool otherChanges() const { return isIntegrated; } private: Fossil bridge; filemap_t workspaceFiles; stringset_t pathSet; stashmap_t stashMap; + QStringList branchList; + QStringMap tags; + bool isIntegrated; QStandardItemModel repoFileModel; - QStandardItemModel repoDirModel; - QStandardItemModel repoStashModel; + QStandardItemModel repoTreeModel; }; #endif // WORKSPACE_H Index: src/main.cpp ================================================================== --- src/main.cpp +++ src/main.cpp @@ -4,11 +4,11 @@ int main(int argc, char *argv[]) { QApplication app(argc, argv); app.setApplicationName("Fuel"); app.setApplicationVersion("2.0.0"); - app.setOrganizationDomain("fuelscm.org"); + app.setOrganizationDomain("fuel-scm.org"); app.setOrganizationName("Fuel-SCM"); #ifdef Q_OS_MACX // Native OSX applications don't have menu icons Index: ui/BrowserWidget.ui ================================================================== --- ui/BrowserWidget.ui +++ ui/BrowserWidget.ui @@ -12,11 +12,20 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -42,11 +51,11 @@ - :/icons/icons/Button Previous-01.png:/icons/icons/Button Previous-01.png + :/icons/icon-action-previous:/icons/icon-action-previous Back @@ -54,11 +63,11 @@ - :/icons/icons/Button Next-01.png:/icons/icons/Button Next-01.png + :/icons/icon-action-next:/icons/icon-action-next Forward @@ -66,11 +75,11 @@ - :/icons/icons/Button Refresh-01.png:/icons/icons/Button Refresh-01.png + :/icons/icon-action-refresh:/icons/icon-action-refresh Refresh @@ -78,11 +87,11 @@ - :/icons/icons/Button Close-01.png:/icons/icons/Button Close-01.png + :/icons/icon-action-stop:/icons/icon-action-stop Stop Index: ui/CommitDialog.ui ================================================================== --- ui/CommitDialog.ui +++ ui/CommitDialog.ui @@ -8,11 +8,11 @@ 0 0 400 - 300 + 394 Commit @@ -60,13 +60,76 @@ - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + New branch + + + + + + + + + + + + + + false + + + + + + + + + + false + + + + + + + Branch name + + + + + + + Private branch + + + + + + + + - + Revert stashed files ADDED ui/FslSettingsDialog.ui Index: ui/FslSettingsDialog.ui ================================================================== --- /dev/null +++ ui/FslSettingsDialog.ui @@ -0,0 +1,300 @@ + + + FslSettingsDialog + + + Qt::WindowModal + + + + 0 + 0 + 457 + 266 + + + + Fossil Settings + + + + :/icons/icon-application:/icons/icon-application + + + true + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 0 + + + + Graphical Diff + + + + + + + + + Path to graphical diff tool + + + + + + + + 0 + 0 + + + + + 24 + 24 + + + + ... + + + + + + + + + + 100 + 0 + + + + Graphical Merge + + + + + + + + + Path to the graphical merge tool + + + + + + + + 0 + 0 + + + + + 24 + 24 + + + + ... + + + + + + + + + + 100 + 0 + + + + HTTP Proxy + + + + + + + + 0 + 0 + + + + The URL of the HTTP proxy + + + + + + + HTTP Port + + + + + + + HTTP port to use for the Fossil web interface + + + + + + + + 0 + 0 + + + + A comma separated list of glob-style file patterns to exclude from Fossil's CR/NL consistency checking + + + + + + + + 100 + 0 + + + + Ignore CR/NL + + + + + + + + 0 + 0 + + + + A comma separated list of glob-style file/path patterns ignored in Fossil file operations + + + + + + + + 100 + 0 + + + + Ignore List + + + + + + + + 0 + 0 + + + + The remote url used to push/pull changes. +URL style user names and passwords are also supported. +For example http://username:password@server.com/fossil + + + + + + + + 100 + 0 + + + + Remote Url + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + FslSettingsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + FslSettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + Index: ui/MainWindow.ui ================================================================== --- ui/MainWindow.ui +++ ui/MainWindow.ui @@ -15,12 +15,12 @@ Fuel - - :/icons/icons/Battery-01.png:/icons/icons/Battery-01.png + + :/icons/icon-application:/icons/icon-application true @@ -64,11 +64,11 @@ 20 0 - Qt::ActionsContextMenu + Qt::CustomContextMenu QAbstractItemView::NoEditTriggers @@ -76,15 +76,12 @@ QAbstractItemView::SelectItems - true + false - - true - false @@ -138,48 +135,10 @@ 30 - - - - 20 - 0 - - - - Qt::ActionsContextMenu - - - QAbstractItemView::NoEditTriggers - - - true - - - QAbstractItemView::SelectRows - - - false - - - true - - - false - - - false - - - true - - - false - - 0 @@ -188,11 +147,11 @@ QTabWidget::South - 1 + 0 Log @@ -261,11 +220,11 @@ 0 0 865 - 22 + 23 &File @@ -293,17 +252,38 @@ - - + + + + + + &Workspace + + + + + + + + + + + + + + + + + Main Toolbar @@ -341,12 +321,10 @@ - - @@ -354,15 +332,15 @@ - - :/icons/icons/Button Refresh-01.png:/icons/icons/Button Refresh-01.png + + :/icons/icon-action-refresh:/icons/icon-action-refresh - Refresh + &Refresh Refresh the views @@ -372,15 +350,15 @@ F5 - - :/icons/icons/Save-01.png:/icons/icons/Save-01.png + + :/icons/icon-action-commit:/icons/icon-action-commit - Commit + &Commit Commit modifications @@ -390,12 +368,12 @@ Ctrl+M - - :/icons/icons/Document Copy-01.png:/icons/icons/Document Copy-01.png + + :/icons/icon-item-diff:/icons/icon-item-diff Diff @@ -408,12 +386,12 @@ Ctrl+D - - :/icons/icons/File New-01.png:/icons/icons/File New-01.png + + :/icons/icon-item-add:/icons/icon-item-add Add @@ -426,12 +404,12 @@ Ctrl++ - - :/icons/icons/File Delete-01.png:/icons/icons/File Delete-01.png + + :/icons/icon-item-delete:/icons/icon-item-delete Delete @@ -444,12 +422,12 @@ Ctrl+- - - :/icons/icons/Document Blank-01.png:/icons/icons/Document Blank-01.png + + :/icons/icon-action-repo-new:/icons/icon-action-repo-new &New... @@ -462,12 +440,12 @@ Ctrl+N - - :/icons/icons/My Documents-01.png:/icons/icons/My Documents-01.png + + :/icons/icon-action-repo-open:/icons/icon-action-repo-open &Open... @@ -494,27 +472,27 @@ Close the current workspace - - :/icons/icons/My Websites-01.png:/icons/icons/My Websites-01.png + + :/icons/icon-action-repo-clone:/icons/icon-action-repo-clone - Clone... + C&lone... Clone a remote repository - - :/icons/icons/Button Upload-01.png:/icons/icons/Button Upload-01.png + + :/icons/icon-action-push:/icons/icon-action-push - Push + &Push Push changes to the remote repository @@ -524,15 +502,15 @@ Ctrl+P - - :/icons/icons/Button Download-01.png:/icons/icons/Button Download-01.png + + :/icons/icon-action-pull:/icons/icon-action-pull - Pull + Pu&ll Pull changes from the remote repository @@ -542,12 +520,12 @@ Ctrl+L - - :/icons/icons/File Open-01.png:/icons/icons/File Open-01.png + + :/icons/icon-item-rename:/icons/icon-item-rename Rename @@ -560,12 +538,12 @@ Ctrl+R - - :/icons/icons/Button Turn Off-01.png:/icons/icons/Button Turn Off-01.png + + :/icons/icon-action-quit:/icons/icon-action-quit &Quit @@ -581,12 +559,12 @@ true - - :/icons/icons/File History-01.png:/icons/icons/File History-01.png + + :/icons/icon-item-history:/icons/icon-item-history History @@ -602,12 +580,12 @@ true - - :/icons/icons/Network MAC-01.png:/icons/icons/Network MAC-01.png + + :/icons/icon-webview:/icons/icon-webview Fossil UI @@ -617,12 +595,12 @@ Starts the Fossil UI in the Web Browser - - :/icons/icons/Document-Revert-icon.png:/icons/icons/Document-Revert-icon.png + + :/icons/icon-item-revert:/icons/icon-item-revert Revert @@ -632,12 +610,12 @@ Revert files to the last commited version - - :/icons/icons/Text Edit.png:/icons/icons/Text Edit.png + + :/icons/icon-clear-log:/icons/icon-clear-log Clear Log @@ -647,12 +625,12 @@ Ctrl+K - - :/icons/icons/Clock-01.png:/icons/icons/Clock-01.png + + :/icons/icon-action-timeline:/icons/icon-action-timeline Timeline @@ -662,12 +640,12 @@ Display the change history of the Fossil repository - - :/icons/icons/Document-01.png:/icons/icons/Document-01.png + + :/icons/icon-item-file:/icons/icon-item-file Open File @@ -680,12 +658,12 @@ Ctrl+Return - - :/icons/icons/Folder-01.png:/icons/icons/Folder-01.png + + :/icons/icon-action-folder-explore:/icons/icon-action-folder-explore Open Containing @@ -698,15 +676,15 @@ Alt+Return - - :/icons/icons/Button Reload-01.png:/icons/icons/Button Reload-01.png + + :/icons/icon-action-undo:/icons/icon-action-undo - Undo + U&ndo Undo the last Fossil action @@ -716,12 +694,12 @@ Ctrl+Z - - :/icons/icons/Battery-01.png:/icons/icons/Battery-01.png + + :/icons/icon-application:/icons/icon-application &About... @@ -731,18 +709,18 @@ QAction::AboutRole - - :/icons/icons/Button Play-01.png:/icons/icons/Button Play-01.png + + :/icons/icon-action-update:/icons/icon-action-update - Update + &Update - Update the workspace to the latest version + Update the workspace to a revision Update the workspace to the latest version @@ -749,12 +727,12 @@ Ctrl+U - - :/icons/icons/Gear-01.png:/icons/icons/Gear-01.png + + :/icons/icon-action-settings:/icons/icon-action-settings &Preferences... @@ -837,12 +815,12 @@ View the workspace as a list of files - - :/icons/icons/Folder-01.png:/icons/icons/Folder-01.png + + :/icons/icon-item-folder:/icons/icon-item-folder Open Folder @@ -852,12 +830,12 @@ Open the selected folder - - :/icons/icons/Folder Open-01.png:/icons/icons/Folder Open-01.png + + :/icons/icon-action-folder-rename:/icons/icon-action-folder-rename Rename Folder @@ -865,26 +843,26 @@ Rename the selected folder - + - - :/icons/icons/Folder Add-01.png:/icons/icons/Folder Add-01.png + + :/icons/icon-action-stash-new:/icons/icon-action-stash-new - Stash changes + &Stash Changes Stash changes - - :/icons/icons/Folder Open-01.png:/icons/icons/Folder Open-01.png + + :/icons/icon-action-stash-apply:/icons/icon-action-stash-apply Apply Stash @@ -892,44 +870,115 @@ Apply stashed changes - - - true - - - &Stashed Changes - - - View Stashed Changes - - - View Stashed Changes - - - Show the list of stashed changes - - - - :/icons/icons/Folder Delete-01.png:/icons/icons/Folder Delete-01.png + + :/icons/icon-action-stash-delete:/icons/icon-action-stash-delete Delete Stash - - :/icons/icons/Folder Explorer-01.png:/icons/icons/Folder Explorer-01.png + + :/icons/icon-action-stash-diff:/icons/icon-action-stash-diff Diff Stash + + + + + :/icons/icon-action-tag-new:/icons/icon-action-tag-new + + + Create &Tag + + + Create a tag for a revision + + + Create a tag for a revision + + + + + + :/icons/icon-action-tag-delete:/icons/icon-action-tag-delete + + + Delete Tag + + + Delete Tag + + + + + + :/icons/icon-item-branch:/icons/icon-item-branch + + + Create &Branch + + + Create a branch from a revision + + + Create a branch from a revision + + + + + + :/icons/icon-action-merge:/icons/icon-action-merge + + + Merge Branch + + + Merge with a branch + + + Merge with a branch + + + + + true + + + Files and F&olders + + + View files and folders + + + View the workspace as files and folders + + + + + &All + + + Show all files + + + + + + :/icons/icon-action-settings:/icons/icon-action-settings + + + F&ossil Settings + @@ -942,8 +991,10 @@ QWidget
BrowserWidget.h
1
- + + + ADDED ui/RevisionDialog.ui Index: ui/RevisionDialog.ui ================================================================== --- /dev/null +++ ui/RevisionDialog.ui @@ -0,0 +1,129 @@ + + + RevisionDialog + + + Qt::WindowModal + + + + 0 + 0 + 478 + 177 + + + + + 400 + 0 + + + + true + + + + + + + + Revision + + + + + + + true + + + + + + + Name + + + + + + + + + + Integrate + + + + + + + Force + + + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + RevisionDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + RevisionDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + Index: ui/SettingsDialog.ui ================================================================== --- ui/SettingsDialog.ui +++ ui/SettingsDialog.ui @@ -8,400 +8,181 @@ 0 0 457 - 352 + 204 Settings + + + :/icons/icon-application:/icons/icon-application + true - - - 0 - - - - - :/icons/icons/Battery-01.png:/icons/icons/Battery-01.png - - - Application - - - - - - - 100 - 0 - - - - Fossil Path - - - - - - - - - Path to the Fossil executable. Leave blank to use the default Fossil - - - - - - - - 0 - 0 - - - - - 24 - 24 - - - - ... - - - - - - - - - - 100 - 0 - - - - Graphical Diff - - - - - - - - - Path to graphical diff tool - - - - - - - - 0 - 0 - - - - - 24 - 24 - - - - ... - - - - - - - - - - 100 - 0 - - - - Graphical Merge - - - - - - - - - Path to the graphical merge tool - - - - - - - - 0 - 0 - - - - - 24 - 24 - - - - ... - - - - - - - - - - 100 - 0 - - - - HTTP Proxy - - - - - - - - 0 - 0 - - - - The URL of the HTTP proxy - - - - - - - HTTP Port - - - - - - - HTTP port to use for the Fossil web interface - - - - - - - - 100 - 0 - - - - Commit Messages - - - - - - - - 0 - 0 - - - - Clear the commit message history - - - Clear - - - - - - - Web Browser - - - - - - - - 0 - 0 - - - - Web browser to use for the Fossil web interface - - - - - - - - 100 - 0 - - - - Double-click Action - - - - - - - - 0 - 0 - - - - Action to perfom when double-clicking a file - - - -1 - - - - - - - Language - - - - - - - - 0 - 0 - - - - Language for the user interface - - - - - - - - - :/icons/icons/Book-01.png:/icons/icons/Book-01.png - - - Repository - - - - QFormLayout::ExpandingFieldsGrow - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - 100 - 0 - - - - Remote Url - - - - - - - - 0 - 0 - - - - The remote url used to push/pull changes. -URL style user names and passwords are also supported. -For example http://username:password@server.com/fossil - - - - - - - - 100 - 0 - - - - Ignore List - - - - - - - - 0 - 0 - - - - A comma separated list of glob-style file/path patterns ignored in Fossil file operations - - - - - - - - 100 - 0 - - - - Ignore CR/NL - - - - - - - - 0 - 0 - - - - A comma separated list of glob-style file patterns to exclude from Fossil's CR/NL consistency checking - - - - - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 0 + + + + Fossil Path + + + + + + + + + Path to the Fossil executable. Leave blank to use the default Fossil + + + + + + + + 0 + 0 + + + + + 24 + 24 + + + + ... + + + + + + + + + + 100 + 0 + + + + Commit Messages + + + + + + + + 0 + 0 + + + + Clear the commit message history + + + Clear + + + + + + + Web Browser + + + + + + + + 0 + 0 + + + + Web browser to use for the Fossil web interface + + + + + + + + 100 + 0 + + + + Double-click Action + + + + + + + + 0 + 0 + + + + Action to perfom when double-clicking a file + + + -1 + + + + + + + Language + + + + + + + + 0 + 0 + + + + Language for the user interface + + + +