diff --git a/fuel.pro b/fuel.pro index 789cb24..f606f86 100644 --- a/fuel.pro +++ b/fuel.pro @@ -1,87 +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 - -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 - -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 + diff --git a/manifest b/manifest index 45a76fe..404c4e0 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Set\sversion\sto\s1.0.1 -D 2015-05-12T18:18:33.512 +C Merged\snew\sworkspace +D 2015-05-26T18:03:05.443 F .travis.yml 77966888a81c4ceee1fcc79bce842c9667ad8a35 F debian/changelog eb4304dfcb6bb66850ec740838090eb50ce1249b F debian/compat b6abd567fa79cbe0196d093a067271361dc6ca8b @@ -15,7 +15,7 @@ F dist/win/fuel.iss ef3558dbba409eb194938b930377fc9ee27d319e F doc/Building.txt 17b43fa23da764b5d1b828cc48c5a95e612bbd8f F doc/Changes.txt b03302545e4a6c0b16a30d623a7627f8aef65ef6 F doc/License.txt 4cc77b90af91e615a64ae04893fdffa7939db84c -F fuel.pro 844a18c3faf5239e0d0025d8b7feac3900c28e71 +F fuel.pro b010c4ee3093112003a9d27045927efce5985dab F intl/convert.bat 4222ae403418381452b843929d15259ea9850ab1 x F intl/convert.sh 2ca2179ff53e727f241925b75e19182607883c45 x F intl/de_DE.ts e2faceab920ac60c97bbc6fba038e261d51fc741 @@ -183,37 +183,52 @@ F rsrc/icons/Zoom-01.png 67ca532922e9166325c5c75fce1ca3fbb0d2b6a6 F rsrc/icons/fuel.icns 81e535004b62db801a02f3e15d0a33afc9d4070b F rsrc/icons/fuel.ico eb529ab3332a17b9302ef3e851db5b9ebce2a038 F rsrc/icons/fuel.png 40daf53b7f6bdcdd0d6aa5ef433d078ec5ea4342 -F rsrc/resources.qrc 4098be128fd6c045db933d041fe8844b14643a6f +F rsrc/resources.qrc 388225a5b09c56c12d4cbde8b4d0b4466740fc97 F src/BrowserWidget.cpp 8b8f545cdff4a4188edc698a1b4777f5df46f056 F src/BrowserWidget.h 764d66aa9a93b890298bd0301097739cb4e16597 -F src/CloneDialog.cpp 812ef7d361c16da21540b7047c9d4d5e74f18539 -F src/CloneDialog.h e9f0fc8e5cc5ea2e7c43d6e77b5c4a9cc850b59e -F src/CommitDialog.cpp 5300522ac11bc1096a11a6ce22f8c1665d4afc05 -F src/CommitDialog.h f1ee8db92103164e7db55a8407ccdcff24571b72 +F src/CloneDialog.cpp 4fc5aa8146ac63ba6ba7341b1635b3025819d708 +F src/CloneDialog.h 8813d91f893eb3eb86a4ea5e50f9a53a0ea07047 +F src/CommitDialog.cpp bbf5fe1c66d28068cc3fd061f4f9f1faa9e89196 +F src/CommitDialog.h 921bf27c0c538ab9e9d6bdc750064337d346270b F src/CustomWebView.cpp b7dd0c41977c2cba005df07ed8967ba6f58d07d9 F src/CustomWebView.h fbc8ee55812d1acb3c3b2bc31be7533e8a112822 F src/FileActionDialog.cpp fcaebf9986f789b3440d5390b3458ad5f86fe0c8 F src/FileActionDialog.h 15db1650b3a13d70bc338371e4c033c66e3b79ce F src/FileTableView.cpp 5ddf8c391c9a3ac449ec61fb1db837b577afeec2 F src/FileTableView.h 03e56d87c2d46411b9762b87f4d301619aaf18df +F src/Fossil.cpp 0d4c50327a61c48506d2d45e28cd6f71f1697ea2 +F src/Fossil.h 31765ef57e20a860914372d56c024033b30aa765 +F src/FslSettingsDialog.cpp f5a34a70ecb0560d2b6eea6bf27e42048548aedd +F src/FslSettingsDialog.h dfe2a61884a55a74cbb9206b6f6b482b979725e7 F src/LoggedProcess.cpp 2a1e5c94bc1e57c8984563e66c210e43a14dc60c F src/LoggedProcess.h 85df7c635c807a5a0e8c4763f17a0752aaff7261 -F src/MainWindow.cpp 6758e29796f411f920a0a59ece5002e4aeefd9f2 -F src/MainWindow.h 77038e9c9fe8a64a1c2dfb8d4c2be7558ab5f372 -F src/SettingsDialog.cpp a46cff5e5dd425e3dbdd15632abfd5829f5562b4 -F src/SettingsDialog.h 4e2790f581e991c744ae9f86580f1972b8c7ff43 -F src/Utils.cpp 9aff456712e4276b49083426301b3b96d3819c77 -F src/Utils.h c546e478a1225a28c99cd4c30f70cf9be9804a2a -F src/main.cpp e1217b2331f1b0fd30756fc80a72f9676f09cf6b +F src/MainWindow.cpp 4cbfa1fdf3092b97649711388576230e4808d50e +F src/MainWindow.h a848462f21423b5c1e0c218cab1805c308299607 +F src/RevisionDialog.cpp 51065c65a07c118dd1a7363da4a55a135d1c6c9c +F src/RevisionDialog.h b718c3009342eaabad39c8a11a253a4e4fef7a73 +F src/SearchBox.cpp d4209c575baa9933e1ce5ed376e785b289a145ba +F src/SearchBox.h 0c78d3a68136dab3e0e71b83ae36f22bd2688ab2 +F src/Settings.cpp 6ab826273b9693bfcd65f0f59b550ae2aa3577f1 +F src/Settings.h 1ff8bb71e19949150e8caa4f5e5f13f8810e496b +F src/SettingsDialog.cpp 25be4c351dd21ea9132321944f42dc0bc22fb128 +F src/SettingsDialog.h b324dfd77ca3ad24fd83588aaf79a7e4c291e716 +F src/Utils.cpp c48e3316f3a0a5d735e8d4953710500358985e32 +F src/Utils.h c2a28611bd77fb35ea3dcf65fb60ed585f68aa3c +F src/Workspace.cpp f68a4ca05d1b7c5c345fbd89527691813593c663 +F src/Workspace.h d6649a3ae1cd0fbad55237030313e85530417271 +F src/main.cpp d8c65ea5e54102e4989fef9fd8cfd4f13ef8a8f0 F tools/git-push.sh 62cc58434cae5b7bcd6bd9d4cce8b08739f31cd7 x F tools/pack.sh d7f38a498c4e9327fecd6a6e5ac27be270d43008 x -F ui/BrowserWidget.ui 5ad98b13773afadb20a1a2c22148aaebe5dbd95d +F ui/BrowserWidget.ui 994ad9ea0e9f5815d6b1a27acc2f6f39164c507f F ui/CloneDialog.ui 4886e7d4f258ea8b852b5eefc860396e35145712 -F ui/CommitDialog.ui 6200f6cabdcf40a20812e811be28e0793f82516f +F ui/CommitDialog.ui aea77347eef82b6b591f31fb058a1bb96193c728 F ui/FileActionDialog.ui 89bb4dc2d0b8adcd41adcb11ec65f2028a09a12d -F ui/MainWindow.ui 8677f5c8bca5bf7561d5f64bfdd0cef5157c6ac7 -F ui/SettingsDialog.ui 2b7c2870e0054b0f4106f495d85d02c0b814df8b -P 964b28f34fae482959ce8a96cbc6106f8702fb33 -R 52b73271a5ba6efcdf8cd8ac7e8910df +F ui/FslSettingsDialog.ui 042717833d8efea905b4fc380bad580be809717d +F ui/MainWindow.ui 2d36b1ba9886356802f2cddf12c4cabb5ee1acde +F ui/RevisionDialog.ui 27c3b98c665fec014a50cbf3352c0627f75e68cd +F ui/SettingsDialog.ui 4c480cd595a32664d01c85bf74845c4282fc0068 +P e2ba8f0aba679a1c52d36daf3041cd139bd49579 7ebac37b3255e6721889c070b7327a96c63b6c97 +R 8f806c4029c039de40ed9c8c7d5c5dca +T +closed 7ebac37b3255e6721889c070b7327a96c63b6c97 U kostas -Z e4adddbe2917b1cd621d67e57ca27d42 +Z 0150f2a9d78e86830f0a9f5b26438204 diff --git a/manifest.uuid b/manifest.uuid index 887f359..0442ec3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e2ba8f0aba679a1c52d36daf3041cd139bd49579 \ No newline at end of file +322729110f48bfb8c007dd611d47e411b389044a \ No newline at end of file diff --git a/rsrc/resources.qrc b/rsrc/resources.qrc index 8c17330..779203f 100644 --- a/rsrc/resources.qrc +++ b/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 + diff --git a/src/CloneDialog.cpp b/src/CloneDialog.cpp index 0f6003d..ff1024b 100644 --- a/src/CloneDialog.cpp +++ b/src/CloneDialog.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "Utils.h" //----------------------------------------------------------------------------- CloneDialog::CloneDialog(QWidget *parent) : diff --git a/src/CloneDialog.h b/src/CloneDialog.h index b2fdae3..884db5a 100644 --- a/src/CloneDialog.h +++ b/src/CloneDialog.h @@ -3,10 +3,6 @@ #include -#define FOSSIL_CHECKOUT1 "_FOSSIL_" -#define FOSSIL_CHECKOUT2 ".fslckout" -#define FOSSIL_EXT "fossil" - namespace Ui { class CloneDialog; } diff --git a/src/CommitDialog.cpp b/src/CommitDialog.cpp index 0322c39..4d0de48 100644 --- a/src/CommitDialog.cpp +++ b/src/CommitDialog.cpp @@ -4,7 +4,7 @@ #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) { @@ -15,16 +15,13 @@ CommitDialog::CommitDialog(QWidget *parent, QString title, QStringList &files, c 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); @@ -77,16 +74,12 @@ CommitDialog::~CommitDialog() } //------------------------------------------------------------------------------ -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; @@ -100,15 +93,40 @@ bool CommitDialog::run(QWidget *parent, QString title, QStringList &files, QStri files.append(si->text()); } - if(checkBoxText) + branchName.clear(); + if(dlg.ui->chkNewBranch->isChecked()) { - Q_ASSERT(checkBoxValue); - *checkBoxValue = dlg.ui->checkBox->checkState() == Qt::Checked; + 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; icheckState()!=Qt::Checked) + continue; + stashedFiles.append(si->text()); + } + + revertFiles = dlg.ui->chkRevertFiles->checkState() == Qt::Checked; + return true; +} + //------------------------------------------------------------------------------ void CommitDialog::on_comboBox_activated(int index) { @@ -142,3 +160,10 @@ void CommitDialog::on_listView_clicked(const QModelIndex &) ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(num_selected>0); } + +//------------------------------------------------------------------------------ +void CommitDialog::on_chkNewBranch_toggled(bool checked) +{ + ui->chkPrivateBranch->setEnabled(checked); + ui->lineBranchName->setEnabled(checked); +} diff --git a/src/CommitDialog.h b/src/CommitDialog.h index 4825d11..f52b928 100644 --- a/src/CommitDialog.h +++ b/src/CommitDialog.h @@ -5,26 +5,28 @@ #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; }; diff --git a/src/Fossil.cpp b/src/Fossil.cpp new file mode 100644 index 0000000..148502e --- /dev/null +++ b/src/Fossil.cpp @@ -0,0 +1,1087 @@ +#include "Fossil.h" +#include +#include +#include +#include +#include +#include +#include +#include "Utils.h" + +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; + + // We need to determine the reason why fossil has failed + // so we delay processing of the exit_code + if(!runFossilRaw(QStringList() << "info", &res, &exit_code, RUNFLAGS_SILENT_ALL)) + return REPO_NOT_FOUND; + + bool run_ok = exit_code == EXIT_SUCCESS; + + for(QStringList::iterator it=res.begin(); it!=res.end(); ++it) + { + int col_index = it->indexOf(':'); + if(col_index==-1) + continue; + + QString key = it->left(col_index).trimmed(); + QString value = it->mid(col_index+1).trimmed(); + + if(key=="fossil") + { + if(value=="incorrect repository schema version") + return REPO_OLD_SCHEMA; + else if(value=="not within an open checkout") + return REPO_NOT_FOUND; + } + + if(run_ok) + { + 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; +} + +//------------------------------------------------------------------------------ +bool Fossil::openRepository(const QString& repositoryPath, const QString& workspacePath) +{ + QFileInfo fi(repositoryPath); + + if(!QDir::setCurrent(workspacePath) || !fi.isFile()) + return false; + + QString abspath = fi.absoluteFilePath(); + setCurrentWorkspace(workspacePath); + setRepositoryFile(abspath); + + if(!runFossil(QStringList() << "open" << QuotePath(abspath))) + return false; + + return true; +} + +//------------------------------------------------------------------------------ +bool Fossil::newRepository(const QString& repositoryPath) +{ + QFileInfo fi(repositoryPath); + + if(fi.exists()) + return false; + + if(!runFossil(QStringList() << "new" << QuotePath(fi.absoluteFilePath()))) + return false; + return true; +} + +//------------------------------------------------------------------------------ +bool Fossil::closeRepository() +{ + if(!runFossil(QStringList() << "close")) + return false; + + stopUI(); + setCurrentWorkspace(""); + return true; +} + +//------------------------------------------------------------------------------ +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"); +} + +//------------------------------------------------------------------------------ +bool Fossil::pullRepository() +{ + return runFossil(QStringList() << "pull"); +} + +//------------------------------------------------------------------------------ +bool Fossil::cloneRepository(const QString& repository, const QUrl& url, const QUrl& proxyUrl) +{ + // Actual command + QStringList cmd = QStringList() << "clone"; + + // Log Command + QStringList logcmd = QStringList() << "fossil" << "clone"; + + QString source = url.toString(); + QString logsource = url.toString(QUrl::RemovePassword); + if(url.isLocalFile()) + { + source = url.toLocalFile(); + logsource = source; + } + cmd << source << repository; + logcmd << logsource << repository; + + if(!proxyUrl.isEmpty()) + { + cmd << "--proxy" << proxyUrl.toString(); + logcmd << "--proxy" << proxyUrl.toString(QUrl::RemovePassword); + } + + log(">"+logcmd.join(" ")+"
", true); + + // Clone Repo + if(!runFossil(cmd, 0, RUNFLAGS_SILENT_INPUT)) + return false; + + return true; +} + +//------------------------------------------------------------------------------ +bool Fossil::getFossilVersion(QString& version) +{ + QStringList res; + if(!runFossil(QStringList() << "version", &res, RUNFLAGS_SILENT_ALL) && res.length()==1) + return false; + + if(res.length()==0) + return false; + + int off = res[0].indexOf("version "); + if(off==-1) + return false; + + version = res[0].mid(off+8); + return true; +} + +//------------------------------------------------------------------------------ +bool Fossil::diffFile(const QString &repoFile) +{ + // Run the diff detached + return runFossil(QStringList() << "gdiff" << QuotePath(repoFile), 0, RUNFLAGS_DETACHED); +} + +//------------------------------------------------------------------------------ +bool Fossil::commitFiles(const QStringList& fileList, const QString& comment, const QString &newBranchName, bool isPrivateBranch) +{ + // Do commit + QString comment_fname; + { + QTemporaryFile temp_file; + if(!temp_file.open()) + return false; + + comment_fname = temp_file.fileName(); + } + + QFile comment_file(comment_fname); + if(!comment_file.open(QIODevice::WriteOnly)) + return false; + + // Write BOM + comment_file.write(reinterpret_cast(UTF8_BOM), sizeof(UTF8_BOM)); + + // Write Comment + comment_file.write(comment.toUtf8()); + 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; +} + +//------------------------------------------------------------------------------ +bool Fossil::addFiles(const QStringList& fileList) +{ + if(fileList.empty()) + return false; + + // Do Add + return runFossil(QStringList() << "add" << QuotePaths(fileList)); +} + +//------------------------------------------------------------------------------ +bool Fossil::removeFiles(const QStringList& fileList, bool deleteLocal) +{ + if(fileList.empty()) + return false; + + // Do Delete + if(!runFossil(QStringList() << "delete" << QuotePaths(fileList))) + return false; + + if(deleteLocal) + { + for(int i=0; i0) + url = out[0].trimmed(); + return true; +} + +//------------------------------------------------------------------------------ +bool Fossil::stashNew(const QStringList& fileList, const QString& name, bool revert) +{ + // Do Stash + // Snapshot just records the changes into the stash + QString command = "snapshot"; + + // Save also reverts the stashed files + if(revert) + command = "save"; + + return runFossil(QStringList() << "stash" << command << "-m" << name << QuotePaths(fileList)); +} + +//------------------------------------------------------------------------------ +bool Fossil::stashList(stashmap_t& stashes) +{ + stashes.clear(); + QStringList res; + + if(!runFossil(QStringList() << "stash" << "ls", &res, RUNFLAGS_SILENT_ALL)) + return false; + + for(QStringList::iterator line_it=res.begin(); line_it!=res.end(); ) + { + QString line = *line_it; + + int index = REGEX_STASH.indexIn(line); + if(index==-1) + break; + + QString id = REGEX_STASH.cap(1); + ++line_it; + + QString name; + // Finish at an anonymous stash or start of a new stash ? + if(line_it==res.end() || REGEX_STASH.indexIn(*line_it)!=-1) + name = line.trimmed(); + else // Named stash + { + // Parse stash name + name = (*line_it); + name = name.trimmed(); + ++line_it; + } + + stashes.insert(name, id); + } + + return true; +} + +//------------------------------------------------------------------------------ +bool Fossil::stashApply(const QString& name) +{ + return runFossil(QStringList() << "stash" << "apply" << name); +} + +//------------------------------------------------------------------------------ +bool Fossil::stashDrop(const QString& name) +{ + return runFossil(QStringList() << "stash" << "drop" << name); +} + +//------------------------------------------------------------------------------ +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 + int qend = line.lastIndexOf('('); + if(qend == -1) + qend = line.lastIndexOf('['); + Q_ASSERT(qend!=-1); + line = line.left(qend); + line = line.trimmed(); + line += "?"; + line[0]=QString(line[0]).toUpper()[0]; + return line; +} + +//------------------------------------------------------------------------------ +bool Fossil::runFossil(const QStringList &args, QStringList *output, int runFlags) +{ + int exit_code = EXIT_FAILURE; + if(!runFossilRaw(args, output, &exit_code, runFlags)) + return false; + + return exit_code == EXIT_SUCCESS; +} + +//------------------------------------------------------------------------------ +// Run fossil. Returns true if execution was successful regardless if fossil +// issued an error +bool Fossil::runFossilRaw(const QStringList &args, QStringList *output, int *exitCode, int runFlags) +{ + bool silent_input = (runFlags & RUNFLAGS_SILENT_INPUT) != 0; + bool silent_output = (runFlags & RUNFLAGS_SILENT_OUTPUT) != 0; + bool detached = (runFlags & RUNFLAGS_DETACHED) != 0; + + if(!silent_input) + { + QString params; + foreach(QString p, args) + { + if(p.indexOf(' ')!=-1) + params += '"' + p + "\" "; + else + params += p + ' '; + } + log("> fossil "+params+"
", true); + } + + QString wkdir = getCurrentWorkspace(); + + QString fossil = getFossilPath(); + + // Detached processes use the command-line only, to avoid having to wait + // for the temporary args file to be released before returing + if(detached) + return QProcess::startDetached(fossil, args, wkdir); + + // Make status message + QString status_msg = QObject::tr("Running Fossil"); + if(args.length() > 0) + status_msg = QString("Fossil %0").arg(args[0].toCaseFolded()); + ScopedStatus status(uiCallback, status_msg); + + // Generate args file + const QStringList *final_args = &args; + QTemporaryFile args_file; + if(!args_file.open()) + { + log(QObject::tr("Could not generate command line file")); + return false; + } + + // Write BOM + args_file.write(reinterpret_cast(UTF8_BOM), sizeof(UTF8_BOM)); + + // Write Args + foreach(const QString &arg, args) + { + args_file.write(arg.toUtf8()); + args_file.write("\n"); + } + args_file.close(); + + // Replace args with args filename + QStringList run_args; + run_args.append("--args"); + run_args.append(args_file.fileName()); + final_args = &run_args; + + // Create fossil process + // FIXME: when we are sure this works delete this + // LoggedProcess process(parentWidget*/); + LoggedProcess process(0); + + process.setWorkingDirectory(wkdir); + + process.start(fossil, *final_args); + if(!process.waitForStarted()) + { + log(QObject::tr("Could not start Fossil executable '%0'").arg(fossil)+"\n"); + return false; + } + const QChar EOL_MARK('\n'); + QString ans_yes = 'y' + EOL_MARK; + QString ans_no = 'n' + EOL_MARK; + QString ans_always = 'a' + EOL_MARK; + QString ans_convert = 'c' + EOL_MARK; + + operationAborted = false; + QString buffer; + +#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); + + Q_ASSERT(uiCallback); +#ifdef QT_DEBUG + size_t input_index = 0; +#endif + + while(true) + { + QProcess::ProcessState state = process.state(); + qint64 bytes_avail = process.logBytesAvailable(); + + if(state!=QProcess::Running && bytes_avail<1) + break; + + if(operationAborted) + { + log("\n* "+QObject::tr("Terminated")+" *\n"); + #ifdef Q_OS_WIN // Verify this is still true on Qt5 + process.kill(); // QT on windows cannot terminate console processes with QProcess::terminate + #else + process.terminate(); + #endif + break; + } + + QByteArray input; + process.getLogAndClear(input); + + #ifdef QT_DEBUG // Log fossil output in debug builds + 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; + + // Normalize line endings + buffer = buffer.replace("\r\n", "\n"); + buffer = buffer.replace("\r", "\n"); + + // Extract the last line + int last_line_start = buffer.lastIndexOf(EOL_MARK); + + QString last_line; + QString before_last_line; + if(last_line_start != -1) + { + last_line = buffer.mid(last_line_start+1); // Including the EOL + + // Detect previous line + if(last_line_start>0) + { + int before_last_line_start = buffer.lastIndexOf(EOL_MARK, last_line_start-1); + + // 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); + } + } + else + last_line = buffer; + + last_line = last_line.trimmed(); + + // Check if we have a query + bool ends_qmark = !last_line.isEmpty() && last_line[last_line.length()-1]=='?'; + bool have_yn_query = last_line.toLower().indexOf("y/n")!=-1; + bool have_yna_query = last_line.toLower().indexOf("a=always/y/n")!=-1 || last_line.toLower().indexOf("yes/no/all")!=-1 || last_line.toLower().indexOf("a=all/y/n")!=-1; + bool have_an_query = last_line.toLower().indexOf("a=always/n")!=-1; + bool have_acyn_query = last_line.toLower().indexOf("a=all/c=convert/y/n")!=-1; + + bool have_query = ends_qmark && (have_yn_query || have_yna_query || have_an_query || have_acyn_query); + + // Flush all complete lines to the log and output + QStringList log_lines = buffer.left(last_line_start).split(EOL_MARK); + for(int l=0; lappend(line); + + if(!silent_output) + log(line+"\n"); + } + + // Remove everything we processed (including the EOL) + buffer = buffer.mid(last_line_start+1) ; + + // Now process any query + if(have_query && (have_yna_query || have_acyn_query)) + { + log(last_line); + QString query = ParseFossilQuery(last_line); + QMessageBox::StandardButtons buttons = QMessageBox::YesToAll|QMessageBox::Yes|QMessageBox::No; + + // Add any extra text available to the query + before_last_line = before_last_line.trimmed(); + if(!before_last_line.isEmpty()) + query = before_last_line + "\n" + query; + + // Map the Convert option to the Apply button + if(have_acyn_query) + buttons |= QMessageBox::Apply; + + QMessageBox::StandardButton res = uiCallback->Query("Fossil", query, buttons); + if(res==QMessageBox::Yes) + { + process.write(ans_yes.toLatin1()); + log("Y\n"); + } + else if(res==QMessageBox::YesAll) + { + process.write(ans_always.toLatin1()); + log("A\n"); + } + else if(res==QMessageBox::Apply) + { + process.write(ans_convert.toLatin1()); + log("C\n"); + } + else + { + process.write(ans_no.toLatin1()); + log("N\n"); + } + buffer.clear(); + } + else if(have_query && have_yn_query) + { + log(last_line); + QString query = ParseFossilQuery(last_line); + QMessageBox::StandardButton res = uiCallback->Query("Fossil", query, QMessageBox::Yes|QMessageBox::No); + + if(res==QMessageBox::Yes) + { + process.write(ans_yes.toLatin1()); + log("Y\n"); + } + else + { + process.write(ans_no.toLatin1()); + log("N\n"); + } + + buffer.clear(); + } + else if(have_query && have_an_query) + { + log(last_line); + QString query = ParseFossilQuery(last_line); + QMessageBox::StandardButton res = uiCallback->Query("Fossil", query, QMessageBox::YesToAll|QMessageBox::No); + if(res==QMessageBox::YesAll) + { + process.write(ans_always.toLatin1()); + log("A\n"); + } + else + { + process.write(ans_no.toLatin1()); + log("N\n"); + } + buffer.clear(); + } + } + + delete decoder; + + // Must be finished by now + Q_ASSERT(process.state()==QProcess::NotRunning); + + QProcess::ExitStatus es = process.exitStatus(); + + if(es!=QProcess::NormalExit) + return false; + + if(exitCode) + *exitCode = process.exitCode(); + + return true; +} + +//------------------------------------------------------------------------------ +QString Fossil::getFossilPath() +{ + // Use the user-specified fossil if available + QString fossil_path = fossilPath; + if(!fossil_path.isEmpty()) + return QDir::toNativeSeparators(fossil_path); + + QString fossil_exe = "fossil"; +#ifdef Q_OS_WIN + fossil_exe += ".exe"; +#endif + // Use our fossil if available + QString fuel_fossil = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + QDir::separator() + fossil_exe); + + if(QFile::exists(fuel_fossil)) + return fuel_fossil; + + // Otherwise assume there is a "fossil" executable in the path + return fossil_exe; +} + +//------------------------------------------------------------------------------ +bool Fossil::isWorkspace(const QString &path) +{ + if(path.length()==0) + return false; + + QFileInfo fi(path); + QString wkspace = path; + wkspace = fi.absoluteDir().absolutePath(); + QString checkout_file1 = wkspace + PATH_SEPARATOR + FOSSIL_CHECKOUT1; + QString checkout_file2 = wkspace + PATH_SEPARATOR + FOSSIL_CHECKOUT2; + + return (QFileInfo(checkout_file1).exists() || QFileInfo(checkout_file2).exists()); +} + +//------------------------------------------------------------------------------ +bool Fossil::uiRunning() const +{ + return fossilUI.state() == QProcess::Running; +} + +//------------------------------------------------------------------------------ +bool Fossil::startUI(const QString &httpPort) +{ + if(uiRunning()) + { + log(QObject::tr("Fossil UI is already running")+"\n"); + return true; + } + + // FIXME: when we are sure this works delete this + //fossilUI.setParent(parentWidget); + + fossilUI.setProcessChannelMode(QProcess::MergedChannels); + fossilUI.setWorkingDirectory(getCurrentWorkspace()); + + log("> fossil ui
", true); + log(QObject::tr("Starting Fossil browser UI. Please wait.")+"\n"); + QString fossil = getFossilPath(); + + 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() +{ + if(uiRunning()) + { +#ifdef Q_WS_WIN + fossilUI.kill(); // QT on windows cannot terminate console processes with QProcess::terminate +#else + fossilUI.terminate(); +#endif + } + fossilUI.close(); + fossilUIPort.clear(); +} + +//------------------------------------------------------------------------------ +QString Fossil::getUIHttpAddress() const +{ + if(fossilUIPort.isEmpty()) + return QString(); + return "http://127.0.0.1:"+fossilUIPort; +} diff --git a/src/Fossil.h b/src/Fossil.h new file mode 100644 index 0000000..1925f3d --- /dev/null +++ b/src/Fossil.h @@ -0,0 +1,153 @@ +#ifndef FOSSIL_H +#define FOSSIL_H + +class QStringList; +#include +#include +#include +#include "LoggedProcess.h" +#include "Utils.h" + +typedef QMap stashmap_t; + +#define PATH_SEPARATOR "/" + +enum RunFlags +{ + 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_DEBUG = 1<<3, +}; + +enum RepoStatus +{ + REPO_OK, + REPO_NOT_FOUND, + REPO_OLD_SCHEMA +}; + +class Fossil +{ +public: + Fossil() + : operationAborted(false) + , uiCallback(0) + { + } + + void Init(UICallback *callback) + { + uiCallback = callback; + fossilPath.clear(); + currentWorkspace.clear(); + } + + bool runFossil(const QStringList &args, QStringList *output=0, int runFlags=RUNFLAGS_NONE); + bool runFossilRaw(const QStringList &args, QStringList *output, int *exitCode, int runFlags); + + static bool isWorkspace(const QString &path); + + RepoStatus getRepoStatus(); + + void setCurrentWorkspace(const QString &workspace) + { + currentWorkspace = workspace; + } + + const QString &getCurrentWorkspace() const + { + return currentWorkspace; + } + + + const QString &getProjectName() const + { + return projectName; + } + + const QString &getRepositoryFile() const + { + return repositoryFile; + } + + void setRepositoryFile(const QString &filename) + { + repositoryFile = filename; + } + + bool openRepository(const QString &repositoryPath, const QString& workspacePath); + bool newRepository(const QString &repositoryPath); + 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, 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, 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); + bool setFossilSetting(const QString &name, const QString &value, bool global); + bool setRemoteUrl(const QString &url); + bool getRemoteUrl(QString &url); + + bool stashNew(const QStringList& fileList, const QString& name, bool revert); + bool stashList(stashmap_t &stashes); + bool stashApply(const QString& name); + 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); + } + + QString getFossilPath(); + + bool operationAborted; + UICallback *uiCallback; + QString currentWorkspace; + QString fossilPath; // The value from the settings + QString repositoryFile; + QString projectName; + QString currentRevision; + QStringList currentTags; + LoggedProcess fossilUI; + QString fossilUIPort; +}; + + +#endif // FOSSIL_H diff --git a/src/FslSettingsDialog.cpp b/src/FslSettingsDialog.cpp new file mode 100644 index 0000000..62de753 --- /dev/null +++ b/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); +} + diff --git a/src/FslSettingsDialog.h b/src/FslSettingsDialog.h new file mode 100644 index 0000000..ed71046 --- /dev/null +++ b/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 diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 3a7e9ff..09896a2 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -7,32 +7,21 @@ #include #include #include -#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" -#include "LoggedProcess.h" - -#define COUNTOF(array) (sizeof(array)/sizeof(array[0])) - -#define PATH_SEP "/" - -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); +#define REVISION_LATEST "Latest revision" //----------------------------------------------------------------------------- enum @@ -52,70 +41,51 @@ enum enum { - REPODIRMODEL_ROLE_PATH = Qt::UserRole+1 + ROLE_WORKSPACE_ITEM = Qt::UserRole+1 }; -//----------------------------------------------------------------------------- -static QString QuotePath(const QString &path) +struct WorkspaceItem { - return path; -} - -//----------------------------------------------------------------------------- -static QStringList QuotePaths(const QStringList &paths) -{ - QStringList res; - for(int i=0; i QStringMap; -static QStringMap MakeKeyValues(QStringList lines) -{ - QStringMap res; - - foreach(QString l, lines) + enum { - l = l.trimmed(); - int index = l.indexOf(' '); + TYPE_UNKNOWN, + TYPE_WORKSPACE, + TYPE_FOLDER, + TYPE_STASHES, + TYPE_STASH, + TYPE_BRANCHES, + TYPE_BRANCH, + TYPE_TAGS, + TYPE_TAG, + TYPE_REMOTES, + TYPE_SETTINGS + }; - 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; -} - - -/////////////////////////////////////////////////////////////////////////////// -class ScopedStatus -{ -public: - ScopedStatus(const QString &text, Ui::MainWindow *mw, QProgressBar *bar) : ui(mw), progressBar(bar) + WorkspaceItem() + : Type(TYPE_UNKNOWN) { - ui->statusBar->showMessage(text); - progressBar->setHidden(false); } - ~ScopedStatus() + WorkspaceItem(int type, const QString &value) + : Type(type), Value(value) { - ui->statusBar->clearMessage(); - progressBar->setHidden(true); } -private: - Ui::MainWindow *ui; - QProgressBar *progressBar; + + WorkspaceItem(const WorkspaceItem &other) + { + Type = other.Type; + Value = other.Value; + } + + int Type; + QString Value; + + operator QVariant() const + { + return QVariant::fromValue(*this); + } }; +Q_DECLARE_METATYPE(WorkspaceItem) /////////////////////////////////////////////////////////////////////////////// MainWindow::MainWindow(Settings &_settings, QWidget *parent, QString *workspacePath) : @@ -128,59 +98,81 @@ MainWindow::MainWindow(Settings &_settings, QWidget *parent, QString *workspaceP QAction *separator = new QAction(this); separator->setSeparator(true); - // TableView - ui->tableView->setModel(&repoFileModel); + // fileTableView + ui->fileTableView->setModel(&getWorkspace().getFileModel()); - ui->tableView->addAction(ui->actionDiff); - ui->tableView->addAction(ui->actionHistory); - ui->tableView->addAction(ui->actionOpenFile); - ui->tableView->addAction(ui->actionOpenContaining); - ui->tableView->addAction(separator); - ui->tableView->addAction(ui->actionAdd); - ui->tableView->addAction(ui->actionRevert); - ui->tableView->addAction(ui->actionRename); - ui->tableView->addAction(ui->actionDelete); - connect( ui->tableView, + ui->fileTableView->addAction(ui->actionDiff); + ui->fileTableView->addAction(ui->actionHistory); + ui->fileTableView->addAction(ui->actionOpenFile); + ui->fileTableView->addAction(ui->actionOpenContaining); + ui->fileTableView->addAction(separator); + ui->fileTableView->addAction(ui->actionAdd); + ui->fileTableView->addAction(ui->actionRevert); + ui->fileTableView->addAction(ui->actionRename); + ui->fileTableView->addAction(ui->actionDelete); + connect( ui->fileTableView, SIGNAL( dragOutEvent() ), SLOT( onFileViewDragOut() ), Qt::DirectConnection ); QStringList header; header << tr("Status") << tr("File") << tr("Extension") << tr("Modified") << tr("Path"); - repoFileModel.setHorizontalHeaderLabels(header); - repoFileModel.horizontalHeaderItem(COLUMN_STATUS)->setTextAlignment(Qt::AlignCenter); + getWorkspace().getFileModel().setHorizontalHeaderLabels(header); + getWorkspace().getFileModel().horizontalHeaderItem(COLUMN_STATUS)->setTextAlignment(Qt::AlignCenter); // Needed on OSX as the preset value from the GUI editor is not always reflected - ui->tableView->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); + ui->fileTableView->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) - ui->tableView->horizontalHeader()->setMovable(true); + ui->fileTableView->horizontalHeader()->setMovable(true); #else - ui->tableView->horizontalHeader()->setSectionsMovable(true); + ui->fileTableView->horizontalHeader()->setSectionsMovable(true); #endif - ui->tableView->horizontalHeader()->setStretchLastSection(true); + ui->fileTableView->horizontalHeader()->setStretchLastSection(true); - // TreeView - ui->treeView->setModel(&repoDirModel); - connect( ui->treeView->selectionModel(), + // workspaceTreeView + 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( onTreeViewSelectionChanged(const QItemSelection &, const QItemSelection &) ), + SLOT( onWorkspaceTreeViewSelectionChanged(const QItemSelection &, const QItemSelection &) ), Qt::DirectConnection ); - ui->treeView->addAction(ui->actionCommit); - ui->treeView->addAction(ui->actionOpenFolder); - ui->treeView->addAction(ui->actionAdd); - ui->treeView->addAction(ui->actionRevert); - ui->treeView->addAction(ui->actionDelete); - ui->treeView->addAction(separator); - ui->treeView->addAction(ui->actionRenameFolder); - ui->treeView->addAction(ui->actionOpenFolder); + // 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); - // StashView - ui->tableViewStash->setModel(&repoStashModel); - ui->tableViewStash->addAction(ui->actionApplyStash); - ui->tableViewStash->addAction(ui->actionDiffStash); - ui->tableViewStash->addAction(ui->actionDeleteStash); - ui->tableViewStash->horizontalHeader()->setSortIndicatorShown(false); + // 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 @@ -207,6 +199,15 @@ MainWindow::MainWindow(Settings &_settings, QWidget *parent, QString *workspaceP // 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); @@ -214,9 +215,10 @@ MainWindow::MainWindow(Settings &_settings, QWidget *parent, QString *workspaceP 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()) @@ -230,15 +232,43 @@ MainWindow::MainWindow(Settings &_settings, QWidget *parent, QString *workspaceP 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); + applySettings(); // Apply any explicit workspace path if available if(workspacePath && !workspacePath->isEmpty()) openWorkspace(*workspacePath); - abortOperation = false; + operationAborted = false; rebuildRecent(); } @@ -249,16 +279,13 @@ MainWindow::~MainWindow() stopUI(); updateSettings(); - // Dispose RepoFiles - for(filemap_t::iterator it = workspaceFiles.begin(); it!=workspaceFiles.end(); ++it) - delete *it; - delete ui; } + //----------------------------------------------------------------------------- const QString &MainWindow::getCurrentWorkspace() { - return currentWorkspace; + return fossil().getCurrentWorkspace(); } //----------------------------------------------------------------------------- @@ -266,13 +293,14 @@ void MainWindow::setCurrentWorkspace(const QString &workspace) { if(workspace.isEmpty()) { - currentWorkspace.clear(); + fossil().setCurrentWorkspace(""); return; } QString new_workspace = QFileInfo(workspace).absoluteFilePath(); - currentWorkspace = new_workspace; + fossil().setCurrentWorkspace(new_workspace); + addWorkspace(new_workspace); if(!QDir::setCurrent(new_workspace)) @@ -313,8 +341,8 @@ bool MainWindow::openWorkspace(const QString &path) if(fi.isFile()) { wkspace = fi.absoluteDir().absolutePath(); - QString checkout_file1 = wkspace + PATH_SEP + FOSSIL_CHECKOUT1; - QString checkout_file2 = wkspace + PATH_SEP + FOSSIL_CHECKOUT2; + QString checkout_file1 = wkspace + PATH_SEPARATOR + FOSSIL_CHECKOUT1; + QString checkout_file2 = wkspace + PATH_SEPARATOR + FOSSIL_CHECKOUT2; if(!(QFileInfo(checkout_file1).exists() || QFileInfo(checkout_file2).exists()) ) { @@ -329,21 +357,13 @@ bool MainWindow::openWorkspace(const QString &path) return false; } - // Ok open the fossil - setCurrentWorkspace(wkspace); - if(!QDir::setCurrent(wkspace)) - { - QMessageBox::critical(this, tr("Error"), tr("Could not change current directory"), QMessageBox::Ok ); - return false; - } - - repositoryFile = fi.absoluteFilePath(); - - if(!runFossil(QStringList() << "open" << QuotePath(repositoryFile))) + // Ok open the repository file + if(!fossil().openRepository(fi.absoluteFilePath(), wkspace)) { QMessageBox::critical(this, tr("Error"), tr("Could not open repository."), QMessageBox::Ok ); return false; } + } else { @@ -379,6 +399,7 @@ bool MainWindow::openWorkspace(const QString &path) // Select the Root of the tree to update the file view selectRootDir(); + searchBox->clear(); return true; } @@ -440,20 +461,18 @@ void MainWindow::on_actionNewRepository_triggered() stopUI(); on_actionClearLog_triggered(); - repositoryFile = repo_path_info.absoluteFilePath(); - // Create repository - if(!runFossil(QStringList() << "new" << QuotePath(repositoryFile))) + QString repo_abs_path = repo_path_info.absoluteFilePath(); + + if(!fossil().newRepository(repo_abs_path)) { QMessageBox::critical(this, tr("Error"), tr("Could not create repository."), QMessageBox::Ok ); return; } - // Create workspace - setCurrentWorkspace(wkdir); - if(!QDir::setCurrent(wkdir)) + if(!fossil().openRepository(repo_abs_path, wkdir)) { - QMessageBox::critical(this, tr("Error"), tr("Could not change current directory"), QMessageBox::Ok ); + QMessageBox::critical(this, tr("Error"), tr("Could not open repository."), QMessageBox::Ok ); return; } @@ -461,26 +480,20 @@ void MainWindow::on_actionNewRepository_triggered() if(!ui->actionViewUnknown->isChecked()) ui->actionViewUnknown->setChecked(true); - // Open repo - if(!runFossil(QStringList() << "open" << QuotePath(repositoryFile))) - { - QMessageBox::critical(this, tr("Error"), tr("Could not open repository."), QMessageBox::Ok ); - return; - } refresh(); } //------------------------------------------------------------------------------ void MainWindow::on_actionCloseRepository_triggered() { - if(getRepoStatus()!=REPO_OK) + if(fossil().getRepoStatus()!=REPO_OK) return; if(QMessageBox::Yes !=DialogQuery(this, tr("Close Workspace"), tr("Are you sure you want to close this workspace?"))) return; // Close Repo - if(!runFossil(QStringList() << "close")) + if(!fossil().closeRepository()) { QMessageBox::critical(this, tr("Error"), tr("Cannot close the workspace.\nAre there still uncommitted changes available?"), QMessageBox::Ok ); return; @@ -502,32 +515,7 @@ void MainWindow::on_actionCloneRepository_triggered() stopUI(); - // Actual command - QStringList cmd = QStringList() << "clone"; - - // Log Command - QStringList logcmd = QStringList() << "fossil" << "clone"; - - QString source = url.toString(); - QString logsource = url.toString(QUrl::RemovePassword); - if(url.isLocalFile()) - { - source = url.toLocalFile(); - logsource = source; - } - cmd << source << repository; - logcmd << logsource << repository; - - if(!url_proxy.isEmpty()) - { - cmd << "--proxy" << url_proxy.toString(); - logcmd << "--proxy" << url_proxy.toString(QUrl::RemovePassword); - } - - log(">"+logcmd.join(" ")+"
", true); - - // Clone Repo - if(!runFossil(cmd, 0, RUNFLAGS_SILENT_INPUT)) + if(!fossil().cloneRepository(repository, url, url_proxy)) { QMessageBox::critical(this, tr("Error"), tr("Could not clone the repository"), QMessageBox::Ok); return; @@ -552,7 +540,6 @@ void MainWindow::rebuildRecent() recentWorkspaceActs[i]->setData(workspaceHistory[i]); recentWorkspaceActs[i]->setVisible(true); } - } //------------------------------------------------------------------------------ @@ -567,86 +554,67 @@ void MainWindow::onOpenRecent() openWorkspace(workspace); } -//------------------------------------------------------------------------------ -bool MainWindow::scanDirectory(QFileInfoList &entries, const QString& dirPath, const QString &baseDir, const QString ignoreSpec, const bool &abort) -{ - QDir dir(dirPath); - - setStatus(dirPath); - QCoreApplication::processEvents(); - - QFileInfoList list = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot); - for (int i=0; iactionCommit->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"; + // Load repository info - RepoStatus st = getRepoStatus(); + RepoStatus st = fossil().getRepoStatus(); if(st==REPO_NOT_FOUND) { setStatus(tr("No workspace detected.")); enableActions(false); - repoFileModel.removeRows(0, repoFileModel.rowCount()); - repoDirModel.clear(); + getWorkspace().getFileModel().removeRows(0, getWorkspace().getFileModel().rowCount()); + 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); - repoFileModel.removeRows(0, repoFileModel.rowCount()); - repoDirModel.clear(); + getWorkspace().getFileModel().removeRows(0, getWorkspace().getFileModel().rowCount()); + getWorkspace().getTreeModel().removeRows(0, getWorkspace().getFileModel().rowCount()); + setWindowTitle(title); return true; } @@ -655,189 +623,45 @@ bool MainWindow::refresh() setStatus(""); enableActions(true); - QString title = "Fuel"; - if(!projectName.isEmpty()) - title += " - "+projectName; + if(!fossil().getProjectName().isEmpty()) + title += " - " + fossil().getProjectName(); setWindowTitle(title); return true; } + + //------------------------------------------------------------------------------ void MainWindow::scanWorkspace() { - // Scan all workspace files - QFileInfoList all_files; - QString wkdir = getCurrentWorkspace(); - - if(wkdir.isEmpty()) - return; - - // Retrieve the status of files tracked by fossil - QStringList res; - if(!runFossil(QStringList() << "ls" << "-l", &res, RUNFLAGS_SILENT_ALL)) - return; - - bool scan_files = ui->actionViewUnknown->isChecked(); - - setStatus(tr("Scanning Workspace...")); setBusy(true); - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - - // Dispose RepoFiles - for(filemap_t::iterator it = workspaceFiles.begin(); it!=workspaceFiles.end(); ++it) - delete *it; - - workspaceFiles.clear(); - pathSet.clear(); - - abortOperation = false; - - if(scan_files) - { - QCoreApplication::processEvents(); - - QString ignore; - // If we should not be showing ignored files, fill in the ignored spec - if(!ui->actionViewIgnored->isChecked()) - { - // QDir expects multiple specs being separated by a semicolon - ignore = settings.GetFossilValue(FOSSIL_SETTING_IGNORE_GLOB).toString().replace(',',';'); - } - - if(!scanDirectory(all_files, wkdir, wkdir, ignore, abortOperation)) - goto _done; - - for(QFileInfoList::iterator it=all_files.begin(); it!=all_files.end(); ++it) - { - QString filename = it->fileName(); - QString fullpath = it->absoluteFilePath(); - - // Skip fossil files - if(filename == FOSSIL_CHECKOUT1 || filename == FOSSIL_CHECKOUT2 || (!repositoryFile.isEmpty() && QFileInfo(fullpath) == QFileInfo(repositoryFile))) - continue; - - RepoFile *rf = new RepoFile(*it, RepoFile::TYPE_UNKNOWN, wkdir); - workspaceFiles.insert(rf->getFilePath(), rf); - pathSet.insert(rf->getPath()); - } - } - - setStatus(tr("Updating...")); - QCoreApplication::processEvents(); - - // Update Files and Directories - - for(QStringList::iterator line_it=res.begin(); line_it!=res.end(); ++line_it) - { - 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(); - RepoFile::EntryType type = RepoFile::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; - - if(status_text=="EDITED") - type = RepoFile::TYPE_EDITTED; - else if(status_text=="ADDED") - type = RepoFile::TYPE_ADDED; - else if(status_text=="DELETED") - { - type = RepoFile::TYPE_DELETED; - add_missing = true; - } - else if(status_text=="MISSING") - { - type = RepoFile::TYPE_MISSING; - add_missing = true; - } - else if(status_text=="RENAMED") - type = RepoFile::TYPE_RENAMED; - else if(status_text=="UNCHANGED") - type = RepoFile::TYPE_UNCHANGED; - else if(status_text=="CONFLICT") - type = RepoFile::TYPE_CONFLICTED; - - // Filter unwanted file types - if( ((type & RepoFile::TYPE_MODIFIED) && !ui->actionViewModified->isChecked()) || - ((type & RepoFile::TYPE_UNCHANGED) && !ui->actionViewUnchanged->isChecked() )) - { - workspaceFiles.remove(fname); - continue; - } - else - add_missing = true; - - filemap_t::iterator it = workspaceFiles.find(fname); - - RepoFile *rf = 0; - if(add_missing && it==workspaceFiles.end()) - { - QFileInfo info(wkdir+QDir::separator()+fname); - rf = new RepoFile(info, type, wkdir); - workspaceFiles.insert(rf->getFilePath(), rf); - } - - if(!rf) - { - it = workspaceFiles.find(fname); - Q_ASSERT(it!=workspaceFiles.end()); - rf = *it; - } - - rf->setType(type); - - QString path = rf->getPath(); - pathSet.insert(path); - } - - // Load the stash - stashMap.clear(); - res.clear(); - if(!runFossil(QStringList() << "stash" << "ls", &res, RUNFLAGS_SILENT_ALL)) - goto _done; - - for(QStringList::iterator line_it=res.begin(); line_it!=res.end(); ) - { - QString line = *line_it; - - int index = REGEX_STASH.indexIn(line); - if(index==-1) - break; - - QString id = REGEX_STASH.cap(1); - ++line_it; - - QString name; - // Finish at an anonymous stash or start of a new stash ? - if(line_it==res.end() || REGEX_STASH.indexIn(*line_it)!=-1) - name = line.trimmed(); - else // Named stash - { - // Parse stash name - name = (*line_it); - name = name.trimmed(); - ++line_it; - } - - stashMap.insert(name, id); - } - - - // Update the file item model -_done: - updateDirView(); + getWorkspace().scanWorkspace(ui->actionViewUnknown->isChecked(), + ui->actionViewIgnored->isChecked(), + ui->actionViewModified->isChecked(), + ui->actionViewUnchanged->isChecked(), + settings.GetFossilValue(FOSSIL_SETTING_IGNORE_GLOB).toString(), + uiCallback, + operationAborted + ); + updateWorkspaceView(); updateFileView(); - updateStashView(); + + // Build default versions list + versionList.clear(); + versionList += getWorkspace().getBranches(); + versionList += getWorkspace().getTags().keys(); + + selectedTags.clear(); + selectedBranches.clear(); setBusy(false); setStatus(""); - QApplication::restoreOverrideCursor(); + lblRevision->setText(tr("Revision: %0").arg(fossil().getCurrentRevision())); + lblRevision->setVisible(true); + + lblTags->setText(tr("Tags: %0").arg(fossil().getCurrentTags().join(' '))); + lblTags->setVisible(true); } //------------------------------------------------------------------------------ @@ -868,7 +692,7 @@ static void addPathToTree(QStandardItem &root, const QString &path, const QIcon if(!found) // Generate it { QStandardItem *child = new QStandardItem(folderIcon, dir); - child->setData(fullpath); // keep the full path to simplify selection + child->setData(WorkspaceItem(WorkspaceItem::TYPE_FOLDER, fullpath), ROLE_WORKSPACE_ITEM); parent->appendRow(child); parent = child; } @@ -877,169 +701,230 @@ static void addPathToTree(QStandardItem &root, const QString &path, const QIcon } //------------------------------------------------------------------------------ -void MainWindow::updateDirView() +void MainWindow::updateWorkspaceView() { - // Directory View - repoDirModel.clear(); + // 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; - QStringList header; - header << tr("Folders"); - repoDirModel.setHorizontalHeaderLabels(header); + const QItemSelection selection = ui->workspaceTreeView->selectionModel()->selection(); - QStandardItem *root = new QStandardItem(getInternalIcon(":icons/icons/My Documents-01.png"), projectName); - root->setData(""); // Empty Path - root->setEditable(false); - - repoDirModel.appendRow(root); - for(stringset_t::iterator it = pathSet.begin(); it!=pathSet.end(); ++it) + for(name_modelindex_map_t::const_iterator it=name_map.begin(); it!=name_map.end(); ++it) { - const QString &dir = *it; - if(dir.isEmpty()) - continue; + const QModelIndex mi = it.value(); + if(ui->workspaceTreeView->isExpanded(mi)) + expanded_items.insert(it.key()); - addPathToTree(*root, dir, getInternalIcon(":icons/icons/Folder-01.png")); + 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); + } } - ui->treeView->expandToDepth(0); - ui->treeView->sortByColumn(0, Qt::AscendingOrder); } //------------------------------------------------------------------------------ void MainWindow::updateFileView() { // Clear content except headers - repoFileModel.removeRows(0, repoFileModel.rowCount()); + getWorkspace().getFileModel().removeRows(0, getWorkspace().getFileModel().rowCount()); - struct { RepoFile::EntryType type; QString text; const char *icon; } + struct { WorkspaceFile::Type type; QString text; const char *icon; } stats[] = { - { RepoFile::TYPE_EDITTED, tr("Edited"), ":icons/icons/Button Blank Yellow-01.png" }, - { RepoFile::TYPE_UNCHANGED, tr("Unchanged"), ":icons/icons/Button Blank Green-01.png" }, - { RepoFile::TYPE_ADDED, tr("Added"), ":icons/icons/Button Add-01.png" }, - { RepoFile::TYPE_DELETED, tr("Deleted"), ":icons/icons/Button Close-01.png" }, - { RepoFile::TYPE_RENAMED, tr("Renamed"), ":icons/icons/Button Reload-01.png" }, - { RepoFile::TYPE_MISSING, tr("Missing"), ":icons/icons/Button Help-01.png" }, - { RepoFile::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" }, }; 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(filemap_t::iterator it = workspaceFiles.begin(); it!=workspaceFiles.end(); ++it) + for(Workspace::filemap_t::iterator it = getWorkspace().getFiles().begin(); it!=getWorkspace().getFiles().end(); ++it) { - const RepoFile &e = *it.value(); - QString path = e.getPath(); + const WorkspaceFile &e = *it.value(); + 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); - repoFileModel.setItem(item_id, COLUMN_STATUS, status); + 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(); 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; - repoFileModel.setItem(item_id, COLUMN_PATH, new QStandardItem(path)); + 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()); Q_ASSERT(filename_item); // Keep the path in the user data - filename_item->setData(e.getFilePath()); - repoFileModel.setItem(item_id, COLUMN_FILENAME, filename_item); + filename_item->setData(file_path); + getWorkspace().getFileModel().setItem(item_id, COLUMN_FILENAME, filename_item); - repoFileModel.setItem(item_id, COLUMN_EXTENSION, new QStandardItem(finfo.suffix())); - repoFileModel.setItem(item_id, COLUMN_MODIFIED, new QStandardItem(finfo.lastModified().toString(Qt::SystemLocaleShortDate))); + 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))); ++item_id; } - ui->tableView->resizeRowsToContents(); -} - -//------------------------------------------------------------------------------ -MainWindow::RepoStatus MainWindow::getRepoStatus() -{ - QStringList res; - int exit_code = EXIT_FAILURE; - - // We need to determine the reason why fossil has failed - // so we delay processing of the exit_code - if(!runFossilRaw(QStringList() << "info", &res, &exit_code, RUNFLAGS_SILENT_ALL)) - return REPO_NOT_FOUND; - - bool run_ok = exit_code == EXIT_SUCCESS; - - for(QStringList::iterator it=res.begin(); it!=res.end(); ++it) - { - int col_index = it->indexOf(':'); - if(col_index==-1) - continue; - - QString key = it->left(col_index).trimmed(); - QString value = it->mid(col_index+1).trimmed(); - - if(key=="fossil") - { - if(value=="incorrect repository schema version") - return REPO_OLD_SCHEMA; - else if(value=="not within an open checkout") - return REPO_NOT_FOUND; - } - - if(run_ok) - { - if(key=="project-name") - projectName = value; - else if(key=="repository") - repositoryFile = value; - } - } - - return run_ok ? REPO_OK : REPO_NOT_FOUND; -} -//------------------------------------------------------------------------------ -void MainWindow::updateStashView() -{ - repoStashModel.clear(); - - QStringList header; - header << tr("Stashes"); - repoStashModel.setHorizontalHeaderLabels(header); - - for(stashmap_t::iterator it=stashMap.begin(); it!=stashMap.end(); ++it) - { - QStandardItem *item = new QStandardItem(it.key()); - item->setToolTip(it.key()); - repoStashModel.appendRow(item); - } - ui->tableViewStash->resizeColumnsToContents(); - ui->tableViewStash->resizeRowsToContents(); + ui->fileTableView->resizeRowsToContents(); } //------------------------------------------------------------------------------ @@ -1067,333 +952,6 @@ void MainWindow::on_actionClearLog_triggered() ui->textBrowser->clear(); } -//------------------------------------------------------------------------------ -bool MainWindow::runFossil(const QStringList &args, QStringList *output, int runFlags) -{ - int exit_code = EXIT_FAILURE; - if(!runFossilRaw(args, output, &exit_code, runFlags)) - return false; - - return exit_code == EXIT_SUCCESS; -} - -//------------------------------------------------------------------------------ -static QString ParseFossilQuery(QString line) -{ - // Extract question - int qend = line.lastIndexOf('('); - if(qend == -1) - qend = line.lastIndexOf('['); - Q_ASSERT(qend!=-1); - line = line.left(qend); - line = line.trimmed(); - line += "?"; - line[0]=QString(line[0]).toUpper()[0]; - return line; -} - -//------------------------------------------------------------------------------ -// Run fossil. Returns true if execution was successful regardless if fossil -// issued an error -bool MainWindow::runFossilRaw(const QStringList &args, QStringList *output, int *exitCode, int runFlags) -{ - bool silent_input = (runFlags & RUNFLAGS_SILENT_INPUT) != 0; - bool silent_output = (runFlags & RUNFLAGS_SILENT_OUTPUT) != 0; - bool detached = (runFlags & RUNFLAGS_DETACHED) != 0; - - if(!silent_input) - { - QString params; - foreach(QString p, args) - { - if(p.indexOf(' ')!=-1) - params += '"' + p + "\" "; - else - params += p + ' '; - } - log("> fossil "+params+"
", true); - } - - QString wkdir = getCurrentWorkspace(); - - QString fossil = getFossilPath(); - - // Detached processes use the command-line only, to avoid having to wait - // for the temporary args file to be released before returing - if(detached) - return QProcess::startDetached(fossil, args, wkdir); - - // Make StatusBar message - QString status_msg = tr("Running Fossil"); - if(args.length() > 0) - status_msg = QString("Fossil %0").arg(args[0].toCaseFolded()); - ScopedStatus status(status_msg, ui, progressBar); - - // Generate args file - const QStringList *final_args = &args; - QTemporaryFile args_file; - if(!args_file.open()) - { - QMessageBox::critical(this, tr("Error"), tr("Could not generate command line file"), QMessageBox::Ok ); - return false; - } - - // Write BOM - args_file.write(reinterpret_cast(UTF8_BOM), sizeof(UTF8_BOM)); - - // Write Args - foreach(const QString &arg, args) - { - args_file.write(arg.toUtf8()); - args_file.write("\n"); - } - args_file.close(); - - // Replace args with args filename - QStringList run_args; - run_args.append("--args"); - run_args.append(args_file.fileName()); - final_args = &run_args; - - // Create fossil process - LoggedProcess process(this); - process.setWorkingDirectory(wkdir); - - process.start(fossil, *final_args); - if(!process.waitForStarted()) - { - log(tr("Could not start Fossil executable '%0'").arg(fossil)+"\n"); - return false; - } - const QChar EOL_MARK('\n'); - QString ans_yes = 'y' + EOL_MARK; - QString ans_no = 'n' + EOL_MARK; - QString ans_always = 'a' + EOL_MARK; - QString ans_convert = 'c' + EOL_MARK; - - abortOperation = false; - QString buffer; - -#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); - - while(true) - { - QProcess::ProcessState state = process.state(); - qint64 bytes_avail = process.logBytesAvailable(); - - if(state!=QProcess::Running && bytes_avail<1) - break; - - if(abortOperation) - { - #ifdef Q_OS_WIN // Verify this is still true on Qt5 - process.kill(); // QT on windows cannot terminate console processes with QProcess::terminate - #else - process.terminate(); - #endif - break; - } - - QByteArray input; - process.getLogAndClear(input); - - #ifdef QT_DEBUG // Log fossil output in debug builds - if(!input.isEmpty()) - qDebug() << input; - #endif - - buffer += decoder->toUnicode(input); - - QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - - if(buffer.isEmpty()) - continue; - - // Normalize line endings - buffer = buffer.replace("\r\n", "\n"); - buffer = buffer.replace("\r", "\n"); - - // Extract the last line - int last_line_start = buffer.lastIndexOf(EOL_MARK); - - QString last_line; - QString before_last_line; - if(last_line_start != -1) - { - last_line = buffer.mid(last_line_start+1); // Including the EOL - - // Detect previous line - if(last_line_start>0) - { - int before_last_line_start = buffer.lastIndexOf(EOL_MARK, last_line_start-1); - // No line before ? - if(before_last_line_start==-1) - before_last_line_start = 0; // Use entire line - - // Extract previous line - before_last_line = buffer.mid(before_last_line_start, last_line_start-before_last_line_start); - } - } - else - last_line = buffer; - - last_line = last_line.trimmed(); - - // Check if we have a query - bool ends_qmark = !last_line.isEmpty() && last_line[last_line.length()-1]=='?'; - bool have_yn_query = last_line.toLower().indexOf("y/n")!=-1; - bool have_yna_query = last_line.toLower().indexOf("a=always/y/n")!=-1 || last_line.toLower().indexOf("yes/no/all")!=-1 || last_line.toLower().indexOf("a=all/y/n")!=-1; - bool have_an_query = last_line.toLower().indexOf("a=always/n")!=-1; - bool have_acyn_query = last_line.toLower().indexOf("a=all/c=convert/y/n")!=-1; - - bool have_query = ends_qmark && (have_yn_query || have_yna_query || have_an_query || have_acyn_query); - - // Flush all complete lines to the log and output - QStringList log_lines = buffer.left(last_line_start).split(EOL_MARK); - for(int l=0; lappend(line); - - if(!silent_output) - log(line+"\n"); - } - - // Remove everything we processed (including the EOL) - buffer = buffer.mid(last_line_start+1) ; - - // Now process any query - if(have_query && (have_yna_query || have_acyn_query)) - { - log(last_line); - QString query = ParseFossilQuery(last_line); - QMessageBox::StandardButtons buttons = QMessageBox::YesToAll|QMessageBox::Yes|QMessageBox::No; - - // Add any extra text available to the query - before_last_line = before_last_line.trimmed(); - if(!before_last_line.isEmpty()) - query = before_last_line + "\n" + query; - - // Map the Convert option to the Apply button - if(have_acyn_query) - buttons |= QMessageBox::Apply; - - QMessageBox::StandardButton res = DialogQuery(this, "Fossil", query, buttons); - if(res==QMessageBox::Yes) - { - process.write(ans_yes.toLatin1()); - log("Y\n"); - } - else if(res==QMessageBox::YesAll) - { - process.write(ans_always.toLatin1()); - log("A\n"); - } - else if(res==QMessageBox::Apply) - { - process.write(ans_convert.toLatin1()); - log("C\n"); - } - else - { - process.write(ans_no.toLatin1()); - log("N\n"); - } - buffer.clear(); - } - else if(have_query && have_yn_query) - { - log(last_line); - QString query = ParseFossilQuery(last_line); - QMessageBox::StandardButton res = DialogQuery(this, "Fossil", query); - - if(res==QMessageBox::Yes) - { - process.write(ans_yes.toLatin1()); - log("Y\n"); - } - else - { - process.write(ans_no.toLatin1()); - log("N\n"); - } - - buffer.clear(); - } - else if(have_query && have_an_query) - { - log(last_line); - QString query = ParseFossilQuery(last_line); - QMessageBox::StandardButton res = DialogQuery(this, "Fossil", query, QMessageBox::YesToAll|QMessageBox::No); - if(res==QMessageBox::YesAll) - { - process.write(ans_always.toLatin1()); - log("A\n"); - } - else - { - process.write(ans_no.toLatin1()); - log("N\n"); - } - buffer.clear(); - } - } - - delete decoder; - - // Must be finished by now - Q_ASSERT(process.state()==QProcess::NotRunning); - - QProcess::ExitStatus es = process.exitStatus(); - - if(es!=QProcess::NormalExit) - return false; - - if(exitCode) - *exitCode = process.exitCode(); - - return true; -} - - -//------------------------------------------------------------------------------ -QString MainWindow::getFossilPath() -{ - // Use the user-specified fossil if available - QString fossil_path = settings.GetValue(FUEL_SETTING_FOSSIL_PATH).toString(); - if(!fossil_path.isEmpty()) - return QDir::toNativeSeparators(fossil_path); - - QString fossil_exe = "fossil"; -#ifdef Q_OS_WIN - fossil_exe += ".exe"; -#endif - // Use our fossil if available - QString fuel_fossil = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + QDir::separator() + fossil_exe); - - if(QFile::exists(fuel_fossil)) - return fuel_fossil; - - // Otherwise assume there is a "fossil" executable in the path - return fossil_exe; -} //------------------------------------------------------------------------------ void MainWindow::applySettings() { @@ -1417,20 +975,20 @@ void MainWindow::applySettings() store->endArray(); store->beginReadArray("FileColumns"); - for(int i=0; isetArrayIndex(i); if(store->contains("Width")) { int width = store->value("Width").toInt(); - ui->tableView->setColumnWidth(i, width); + ui->fileTableView->setColumnWidth(i, width); } if(store->contains("Index")) { int index = store->value("Index").toInt(); - int cur_index = ui->tableView->horizontalHeader()->visualIndex(i); - ui->tableView->horizontalHeader()->moveSection(cur_index, index); + int cur_index = ui->fileTableView->horizontalHeader()->visualIndex(i); + ui->fileTableView->horizontalHeader()->moveSection(cur_index, index); } } @@ -1464,14 +1022,10 @@ void MainWindow::applySettings() 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->treeView->setVisible(viewMode == VIEWMODE_TREE); - - if(store->contains("ViewStash")) - ui->actionViewStash->setChecked(store->value("ViewStash").toBool()); - ui->tableViewStash->setVisible(ui->actionViewStash->isChecked()); - + //ui->workspaceTreeView->setVisible(viewMode == VIEWMODE_TREE); } //------------------------------------------------------------------------------ @@ -1491,12 +1045,12 @@ void MainWindow::updateSettings() } store->endArray(); - store->beginWriteArray("FileColumns", repoFileModel.columnCount()); - for(int i=0; ibeginWriteArray("FileColumns", getWorkspace().getFileModel().columnCount()); + for(int i=0; isetArrayIndex(i); - store->setValue("Width", ui->tableView->columnWidth(i)); - int index = ui->tableView->horizontalHeader()->visualIndex(i); + store->setValue("Width", ui->fileTableView->columnWidth(i)); + int index = ui->fileTableView->horizontalHeader()->visualIndex(i); store->setValue("Index", index); } store->endArray(); @@ -1510,16 +1064,16 @@ void MainWindow::updateSettings() 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->treeView->model()->index(0, 0); - ui->treeView->selectionModel()->select(root_index, QItemSelectionModel::Select); + QModelIndex root_index = ui->workspaceTreeView->model()->index(0, 0); + ui->workspaceTreeView->selectionModel()->select(root_index, QItemSelectionModel::Select); } } @@ -1531,7 +1085,7 @@ void MainWindow::fossilBrowse(const QString &fossilUrl) 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) { @@ -1544,7 +1098,7 @@ void MainWindow::fossilBrowse(const QString &fossilUrl) //------------------------------------------------------------------------------ void MainWindow::getSelectionFilenames(QStringList &filenames, int includeMask, bool allIfEmpty) { - if(QApplication::focusWidget() == ui->treeView) + if(QApplication::focusWidget() == ui->workspaceTreeView) getDirViewSelection(filenames, includeMask, allIfEmpty); else getFileViewSelection(filenames, includeMask, allIfEmpty); @@ -1554,21 +1108,26 @@ void MainWindow::getSelectionFilenames(QStringList &filenames, int includeMask, void MainWindow::getSelectionPaths(stringset_t &paths) { // Determine the directories selected - QModelIndexList selection = ui->treeView->selectionModel()->selectedIndexes(); - for(QModelIndexList::iterator mi_it = selection.begin(); mi_it!=selection.end(); ++mi_it) + QModelIndexList selection = ui->workspaceTreeView->selectionModel()->selectedIndexes(); + foreach(const QModelIndex &mi, selection) { - const QModelIndex &mi = *mi_it; - QVariant data = repoDirModel.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) { - for(filemap_t::iterator it=workspaceFiles.begin(); it!=workspaceFiles.end(); ++it) + for(Workspace::filemap_t::iterator it=getWorkspace().getFiles().begin(); it!=getWorkspace().getFiles().end(); ++it) { - const RepoFile &e = *(*it); + const WorkspaceFile &e = *(*it); // Skip unwanted file types if(!(includeMask & e.getType())) @@ -1583,16 +1142,16 @@ void MainWindow::getDirViewSelection(QStringList &filenames, int includeMask, bo // Determine the directories selected stringset_t paths; - QModelIndexList selection = ui->treeView->selectionModel()->selectedIndexes(); + QModelIndexList selection = ui->workspaceTreeView->selectionModel()->selectedIndexes(); if(!(selection.empty() && allIfEmpty)) { getSelectionPaths(paths); } // Select the actual files form the selected directories - for(filemap_t::iterator it=workspaceFiles.begin(); it!=workspaceFiles.end(); ++it) + for(Workspace::filemap_t::iterator it=getWorkspace().getFiles().begin(); it!=getWorkspace().getFiles().end(); ++it) { - const RepoFile &e = *(*it); + const WorkspaceFile &e = *(*it); // Skip unwanted file types if(!(includeMask & e.getType())) @@ -1627,12 +1186,12 @@ void MainWindow::getDirViewSelection(QStringList &filenames, int includeMask, bo //------------------------------------------------------------------------------ void MainWindow::getFileViewSelection(QStringList &filenames, int includeMask, bool allIfEmpty) { - QModelIndexList selection = ui->tableView->selectionModel()->selectedIndexes(); + QModelIndexList selection = ui->fileTableView->selectionModel()->selectedIndexes(); if(selection.empty() && allIfEmpty) { - ui->tableView->selectAll(); - selection = ui->tableView->selectionModel()->selectedIndexes(); - ui->tableView->clearSelection(); + ui->fileTableView->selectAll(); + selection = ui->fileTableView->selectionModel()->selectedIndexes(); + ui->fileTableView->clearSelection(); } for(QModelIndexList::iterator mi_it = selection.begin(); mi_it!=selection.end(); ++mi_it) @@ -1644,11 +1203,11 @@ void MainWindow::getFileViewSelection(QStringList &filenames, int includeMask, b if(mi.column()!=COLUMN_FILENAME) continue; - QVariant data = repoFileModel.data(mi, Qt::UserRole+1); + QVariant data = getWorkspace().getFileModel().data(mi, Qt::UserRole+1); QString filename = data.toString(); - filemap_t::iterator e_it = workspaceFiles.find(filename); - Q_ASSERT(e_it!=workspaceFiles.end()); - const RepoFile &e = *e_it.value(); + Workspace::filemap_t::iterator e_it = getWorkspace().getFiles().find(filename); + Q_ASSERT(e_it!=getWorkspace().getFiles().end()); + const WorkspaceFile &e = *e_it.value(); // Skip unwanted files if(!(includeMask & e.getType())) @@ -1658,39 +1217,36 @@ void MainWindow::getFileViewSelection(QStringList &filenames, int includeMask, b } } //------------------------------------------------------------------------------ -void MainWindow::getStashViewSelection(QStringList &stashNames, bool allIfEmpty) +void MainWindow::getSelectionStashes(QStringList &stashNames) { - QModelIndexList selection = ui->tableViewStash->selectionModel()->selectedIndexes(); - if(selection.empty() && allIfEmpty) - { - ui->tableViewStash->selectAll(); - selection = ui->tableViewStash->selectionModel()->selectedIndexes(); - ui->tableViewStash->clearSelection(); - } + 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 = mi.model()->data(mi, ROLE_WORKSPACE_ITEM); + Q_ASSERT(data.isValid()); + WorkspaceItem tv = data.value(); - if(mi.column()!=0) + if(tv.Type != WorkspaceItem::TYPE_STASH) continue; - QString name = repoStashModel.data(mi).toString(); + + QString name = mi.model()->data(mi, Qt::DisplayRole).toString(); stashNames.append(name); } + } //------------------------------------------------------------------------------ -bool MainWindow::diffFile(QString repoFile) +bool MainWindow::diffFile(const QString &repoFile) { - // Run the diff detached - return runFossil(QStringList() << "gdiff" << QuotePath(repoFile), 0, RUNFLAGS_DETACHED); + return fossil().diffFile(repoFile); } //------------------------------------------------------------------------------ void MainWindow::on_actionDiff_triggered() { QStringList selection; - getSelectionFilenames(selection, RepoFile::TYPE_REPO); + getSelectionFilenames(selection, WorkspaceFile::TYPE_REPO); for(QStringList::iterator it = selection.begin(); it!=selection.end(); ++it) if(!diffFile(*it)) @@ -1700,51 +1256,24 @@ void MainWindow::on_actionDiff_triggered() //------------------------------------------------------------------------------ bool MainWindow::startUI() { - if(uiRunning()) - { - log(tr("Fossil UI is already running")+"\n"); - return true; - } - - fossilUI.setParent(this); - fossilUI.setProcessChannelMode(QProcess::MergedChannels); - fossilUI.setWorkingDirectory(getCurrentWorkspace()); - - log("> fossil ui
", true); - log(tr("Starting Fossil browser UI. Please wait.")+"\n"); - QString fossil = getFossilPath(); - - QString port = settings.GetValue(FUEL_SETTING_HTTP_PORT).toString(); - - fossilUI.start(fossil, QStringList() << "server" << "--localauth" << "-P" << port ); - - if(!fossilUI.waitForStarted() || fossilUI.state()!=QProcess::Running) - { - log(tr("Could not start Fossil executable '%s'").arg(fossil)+"\n"); - ui->actionFossilUI->setChecked(false); - return false; - } - - ui->actionFossilUI->setChecked(true); - return true; + bool started = fossil().startUI(""); + ui->actionFossilUI->setChecked(started); + return started; } - //------------------------------------------------------------------------------ void MainWindow::stopUI() { - if(uiRunning()) - { -#ifdef Q_OS_WIN - fossilUI.kill(); // QT on windows cannot terminate console processes with QProcess::terminate -#else - fossilUI.terminate(); -#endif - } - fossilUI.close(); - + fossil().stopUI(); + ui->webView->load(QUrl("about:blank")); ui->actionFossilUI->setChecked(false); } +//------------------------------------------------------------------------------ +bool MainWindow::uiRunning() const +{ + return fossil().uiRunning(); +} + //------------------------------------------------------------------------------ void MainWindow::on_actionFossilUI_triggered() { @@ -1780,7 +1309,7 @@ void MainWindow::on_actionHistory_triggered() } //------------------------------------------------------------------------------ -void MainWindow::on_tableView_doubleClicked(const QModelIndex &/*index*/) +void MainWindow::on_fileTableView_doubleClicked(const QModelIndex &/*index*/) { int action = settings.GetValue(FUEL_SETTING_FILE_DBLCLICK).toInt(); if(action==FILE_DLBCLICK_ACTION_DIFF) @@ -1814,7 +1343,7 @@ void MainWindow::on_actionPush_triggered() return; } - runFossil(QStringList() << "push"); + fossil().pushRepository(); } //------------------------------------------------------------------------------ @@ -1828,22 +1357,24 @@ void MainWindow::on_actionPull_triggered() return; } - runFossil(QStringList() << "pull"); + fossil().pullRepository(); } //------------------------------------------------------------------------------ void MainWindow::on_actionCommit_triggered() { QStringList commit_files; - getSelectionFilenames(commit_files, RepoFile::TYPE_MODIFIED, true); + 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) @@ -1857,52 +1388,23 @@ void MainWindow::on_actionCommit_triggered() 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 - QString comment_fname; - { - QTemporaryFile temp_file; - if(!temp_file.open()) - { - QMessageBox::critical(this, tr("Error"), tr("Could not generate comment file"), QMessageBox::Ok ); - return; - } - comment_fname = temp_file.fileName(); - } - - QFile comment_file(comment_fname); - if(!comment_file.open(QIODevice::WriteOnly)) - { - QMessageBox::critical(this, tr("Error"), tr("Could not generate comment file"), QMessageBox::Ok ); - return; - } - - // Write BOM - comment_file.write(reinterpret_cast(UTF8_BOM), sizeof(UTF8_BOM)); - - // Write Comment - comment_file.write(msg.toUtf8()); - comment_file.close(); - - // Generate fossil parameters. - QStringList params; - params << "commit" << "--message-file" << QuotePath(comment_fname); + QStringList files; // When a subset of files has been selected, explicitely specify each file. // Otherwise all files will be implicitly committed by fossil. This is necessary // when committing after a merge where fossil thinks that we are trying to do // a partial commit which is not permitted. QStringList all_modified_files; - getAllFilenames(all_modified_files, RepoFile::TYPE_MODIFIED); + getAllFilenames(all_modified_files, WorkspaceFile::TYPE_MODIFIED); if(commit_files.size() != all_modified_files.size()) - params << QuotePaths(commit_files); - - runFossil(params); - QFile::remove(comment_fname); + files = commit_files; + fossil().commitFiles(files, msg, branch_name, private_branch); refresh(); } @@ -1911,7 +1413,7 @@ void MainWindow::on_actionAdd_triggered() { // Get unknown files only QStringList selection; - getSelectionFilenames(selection, RepoFile::TYPE_UNKNOWN); + getSelectionFilenames(selection, WorkspaceFile::TYPE_UNKNOWN); if(selection.empty()) return; @@ -1920,8 +1422,7 @@ void MainWindow::on_actionAdd_triggered() return; // Do Add - runFossil(QStringList() << "add" << QuotePaths(selection) ); - + fossil().addFiles(selection); refresh(); } @@ -1929,10 +1430,10 @@ void MainWindow::on_actionAdd_triggered() void MainWindow::on_actionDelete_triggered() { QStringList repo_files; - getSelectionFilenames(repo_files, RepoFile::TYPE_REPO); + getSelectionFilenames(repo_files, WorkspaceFile::TYPE_REPO); QStringList unknown_files; - getSelectionFilenames(unknown_files, RepoFile::TYPE_UNKNOWN); + getSelectionFilenames(unknown_files, WorkspaceFile::TYPE_UNKNOWN); QStringList all_files = repo_files+unknown_files; @@ -1944,18 +1445,16 @@ void MainWindow::on_actionDelete_triggered() if(!FileActionDialog::run(this, tr("Remove files"), tr("The following files will be removed from the repository.")+"\n"+tr("Are you sure?"), all_files, tr("Also delete the local files"), &remove_local )) return; + // Remove repository files if(!repo_files.empty()) - { - // Do Delete - if(!runFossil(QStringList() << "delete" << QuotePaths(repo_files))) - return; - } + fossil().removeFiles(repo_files, remove_local); + // Remove unknown local files if selected if(remove_local) { - for(int i=0; i0 && res[0]=="No undo or redo is available") return; @@ -2053,7 +1546,7 @@ void MainWindow::on_actionUndo_triggered() return; // Do Undo - runFossil(QStringList() << "undo" ); + fossil().undoRepository(res, false); refresh(); } @@ -2062,14 +1555,9 @@ void MainWindow::on_actionUndo_triggered() void MainWindow::on_actionAbout_triggered() { QString fossil_ver; - QStringList res; - if(runFossil(QStringList() << "version", &res, RUNFLAGS_SILENT_ALL) && res.length()==1) - { - int off = res[0].indexOf("version "); - if(off!=-1) - fossil_ver = tr("Fossil version %0").arg(res[0].mid(off+8)) + "\n"; - } + if(fossil().getFossilVersion(fossil_ver)) + fossil_ver = tr("Fossil version %0").arg(fossil_ver) + "\n"; QString qt_ver = tr("QT version %0").arg(QT_VERSION_STR) + "\n\n"; @@ -2094,21 +1582,13 @@ void MainWindow::on_actionAbout_triggered() //------------------------------------------------------------------------------ void MainWindow::on_actionUpdate_triggered() { - QStringList res; + QStringList selected = selectedBranches + selectedTags; - if(!runFossil(QStringList() << "update" << "--nochange", &res, RUNFLAGS_SILENT_ALL)) - return; + QString revision; + if(!selected.isEmpty()) + revision = selected.first(); - 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 - runFossil(QStringList() << "update" ); - - refresh(); + updateRevision(revision); } //------------------------------------------------------------------------------ @@ -2116,10 +1596,12 @@ void MainWindow::loadFossilSettings() { // Also retrieve the fossil global settings QStringList out; - if(!runFossil(QStringList() << "settings", &out, RUNFLAGS_SILENT_ALL)) + + 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) { @@ -2127,16 +1609,18 @@ void MainWindow::loadFossilSettings() Settings::Setting::SettingType type = it->Type; // Command types we issue directly on fossil - if(type == Settings::Setting::TYPE_FOSSIL_COMMAND) + + if(name == FOSSIL_SETTING_REMOTE_URL) { // Retrieve existing url - QStringList out; - if(runFossil(QStringList() << name, &out, RUNFLAGS_SILENT_ALL) && out.length()==1) - it.value().Value = out[0].trimmed(); - + QString url; + if(fossil().getRemoteUrl(url)) + it.value().Value = url; continue; } + Q_ASSERT(type == Settings::Setting::TYPE_FOSSIL_GLOBAL || type == Settings::Setting::TYPE_FOSSIL_LOCAL); + // Otherwise it must be a fossil setting if(!kv.contains(name)) continue; @@ -2159,11 +1643,19 @@ void MainWindow::loadFossilSettings() //------------------------------------------------------------------------------ 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 @@ -2173,28 +1665,20 @@ void MainWindow::on_actionSettings_triggered() Settings::Setting::SettingType type = it.value().Type; // Command types we issue directly on fossil - if(type == Settings::Setting::TYPE_FOSSIL_COMMAND) + // FIXME: major uglyness with settings management + if(name == FOSSIL_SETTING_REMOTE_URL) { // Run as silent to avoid displaying credentials in the log - runFossil(QStringList() << "remote-url" << QuotePath(it.value().Value.toString()), 0, RUNFLAGS_SILENT_INPUT); + fossil().setRemoteUrl(it.value().Value.toString()); continue; } Q_ASSERT(type == Settings::Setting::TYPE_FOSSIL_GLOBAL || type == Settings::Setting::TYPE_FOSSIL_LOCAL); QString value = it.value().Value.toString(); - QStringList params; - - if(value.isEmpty()) - params << "unset" << name; - else - params << "settings" << name << value; - - if(type == Settings::Setting::TYPE_FOSSIL_GLOBAL) - params << "-global"; - - runFossil(params); + fossil().setFossilSetting(name, value, type == Settings::Setting::TYPE_FOSSIL_GLOBAL); } + } //------------------------------------------------------------------------------ @@ -2221,60 +1705,93 @@ 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->treeView->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::onTreeViewSelectionChanged(const QItemSelection &/*selected*/, const QItemSelection &/*deselected*/) +void MainWindow::onWorkspaceTreeViewSelectionChanged(const QItemSelection &/*selected*/, const QItemSelection &/*deselected*/) { - QModelIndexList selection = ui->treeView->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() { - const QItemSelection &selection = ui->treeView->selectionModel()->selection(); + const QItemSelection &selection = ui->workspaceTreeView->selectionModel()->selection(); if(selection.indexes().count()!=1) return; QModelIndex index = selection.indexes().at(0); - on_treeView_doubleClicked(index); + on_workspaceTreeView_doubleClicked(index); } //------------------------------------------------------------------------------ -void MainWindow::on_treeView_doubleClicked(const QModelIndex &index) +void MainWindow::on_workspaceTreeView_doubleClicked(const QModelIndex &index) { - QString target = repoDirModel.data(index, REPODIRMODEL_ROLE_PATH).toString(); - target = getCurrentWorkspace() + PATH_SEP + 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); @@ -2299,7 +1816,7 @@ void MainWindow::on_actionRenameFolder_triggered() return; } - int dir_start = old_path.lastIndexOf(PATH_SEP); + int dir_start = old_path.lastIndexOf(PATH_SEPARATOR); if(dir_start==-1) dir_start = 0; else @@ -2327,17 +1844,17 @@ void MainWindow::on_actionRenameFolder_triggered() QString new_path = old_path.left(dir_start) + new_name; - if(pathSet.contains(new_path)) + if(getWorkspace().getPaths().contains(new_path)) { QMessageBox::critical(this, tr("Error"), tr("Cannot rename folder.")+"\n" +tr("This folder exists already.")); return; } // Collect the files to be moved - filelist_t files_to_move; + Workspace::filelist_t files_to_move; QStringList new_paths; QStringList operations; - foreach(RepoFile *r, workspaceFiles) + foreach(WorkspaceFile *r, getWorkspace().getFiles()) { if(r->getPath().indexOf(old_path)!=0) continue; @@ -2345,7 +1862,7 @@ void MainWindow::on_actionRenameFolder_triggered() files_to_move.append(r); QString new_dir = new_path + r->getPath().mid(old_path.length()); new_paths.append(new_dir); - QString new_file_path = new_dir + PATH_SEP + r->getFilename(); + QString new_file_path = new_dir + PATH_SEPARATOR + r->getFilename(); operations.append(r->getFilePath() + " -> " + new_file_path); } @@ -2364,10 +1881,10 @@ void MainWindow::on_actionRenameFolder_triggered() Q_ASSERT(files_to_move.length() == new_paths.length()); for(int i=0; igetFilename(); + WorkspaceFile *r = files_to_move[i]; + const QString &new_file_path = new_paths[i] + PATH_SEPARATOR + r->getFilename(); - if(!runFossil(QStringList() << "mv" << QuotePath(r->getFilePath()) << QuotePath(new_file_path))) + if(!fossil().renameFile(r->getFilePath(), new_file_path, false)) { log(tr("Move aborted due to errors")+"\n"); goto _exit; @@ -2380,7 +1897,7 @@ void MainWindow::on_actionRenameFolder_triggered() // First ensure that the target directories exist, and if not make them for(int i=0; igetFilename(); + WorkspaceFile *r = files_to_move[i]; + QString new_file_path = new_paths[i] + PATH_SEPARATOR + r->getFilename(); if(QFile::exists(new_file_path)) { @@ -2421,7 +1938,7 @@ void MainWindow::on_actionRenameFolder_triggered() // Finally delete old files for(int i=0; igetFilePath())+"\n"); @@ -2461,24 +1978,18 @@ const QIcon &MainWindow::getInternalIcon(const char* name) } //------------------------------------------------------------------------------ -void MainWindow::on_actionViewStash_triggered() -{ - ui->tableViewStash->setVisible(ui->actionViewStash->isChecked()); -} - -//------------------------------------------------------------------------------ -void MainWindow::on_actionNewStash_triggered() +void MainWindow::on_actionCreateStash_triggered() { QStringList stashed_files; - getSelectionFilenames(stashed_files, RepoFile::TYPE_MODIFIED, true); + 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(); @@ -2490,7 +2001,7 @@ void MainWindow::on_actionNewStash_triggered() } // Check that this stash does not exist - for(stashmap_t::iterator it=stashMap.begin(); it!=stashMap.end(); ++it) + for(stashmap_t::iterator it=getWorkspace().getStashes().begin(); it!=getWorkspace().getStashes().end(); ++it) { if(stash_name == it.key()) { @@ -2500,11 +2011,8 @@ void MainWindow::on_actionNewStash_triggered() } // Do Stash - QString command = "snapshot"; - if(revert) - command = "save"; + fossil().stashNew(stashed_files, stash_name, revert); - runFossil(QStringList() << "stash" << command << "-m" << stash_name << QuotePaths(stashed_files) ); refresh(); } @@ -2512,7 +2020,10 @@ void MainWindow::on_actionNewStash_triggered() 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)) @@ -2521,10 +2032,10 @@ void MainWindow::on_actionApplyStash_triggered() // Apply stashes for(QStringList::iterator it=stashes.begin(); it!=stashes.end(); ++it) { - stashmap_t::iterator id_it = stashMap.find(*it); - Q_ASSERT(id_it!=stashMap.end()); + stashmap_t::iterator id_it = getWorkspace().getStashes().find(*it); + Q_ASSERT(id_it!=getWorkspace().getStashes().end()); - if(!runFossil(QStringList() << "stash" << "apply" << *id_it)) + if(!fossil().stashApply(*id_it)) { log(tr("Stash application aborted due to errors")+"\n"); return; @@ -2534,10 +2045,10 @@ void MainWindow::on_actionApplyStash_triggered() // Delete stashes for(QStringList::iterator it=stashes.begin(); delete_stashes && it!=stashes.end(); ++it) { - stashmap_t::iterator id_it = stashMap.find(*it); - Q_ASSERT(id_it!=stashMap.end()); + stashmap_t::iterator id_it = getWorkspace().getStashes().find(*it); + Q_ASSERT(id_it!=getWorkspace().getStashes().end()); - if(!runFossil(QStringList() << "stash" << "drop" << *id_it)) + if(!fossil().stashDrop(*id_it)) { log(tr("Stash deletion aborted due to errors")+"\n"); return; @@ -2551,7 +2062,7 @@ void MainWindow::on_actionApplyStash_triggered() void MainWindow::on_actionDeleteStash_triggered() { QStringList stashes; - getStashViewSelection(stashes); + getSelectionStashes(stashes); if(stashes.empty()) return; @@ -2562,10 +2073,10 @@ void MainWindow::on_actionDeleteStash_triggered() // Delete stashes for(QStringList::iterator it=stashes.begin(); it!=stashes.end(); ++it) { - stashmap_t::iterator id_it = stashMap.find(*it); - Q_ASSERT(id_it!=stashMap.end()); + stashmap_t::iterator id_it = getWorkspace().getStashes().find(*it); + Q_ASSERT(id_it!=getWorkspace().getStashes().end()); - if(!runFossil(QStringList() << "stash" << "drop" << *id_it)) + if(!fossil().stashDrop(*id_it)) { log(tr("Stash deletion aborted due to errors")+"\n"); return; @@ -2579,16 +2090,16 @@ void MainWindow::on_actionDeleteStash_triggered() void MainWindow::on_actionDiffStash_triggered() { QStringList stashes; - getStashViewSelection(stashes); + getSelectionStashes(stashes); if(stashes.length() != 1) return; - stashmap_t::iterator id_it = stashMap.find(*stashes.begin()); - Q_ASSERT(id_it!=stashMap.end()); + stashmap_t::iterator id_it = getWorkspace().getStashes().find(*stashes.begin()); + Q_ASSERT(id_it!=getWorkspace().getStashes().end()); // Run diff - runFossil(QStringList() << "stash" << "diff" << *id_it, 0); + fossil().stashDiff(*id_it); } //------------------------------------------------------------------------------ @@ -2622,19 +2133,19 @@ void MainWindow::on_textBrowser_customContextMenuRequested(const QPoint &pos) } //------------------------------------------------------------------------------ -void MainWindow::on_tableView_customContextMenuRequested(const QPoint &pos) +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->tableView->selectionModel()->select(ui->tableView->indexAt(pos), QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Rows); + 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(); @@ -2646,12 +2157,44 @@ void MainWindow::on_tableView_customContextMenuRequested(const QPoint &pos) #endif { QMenu *menu = new QMenu(this); - menu->addActions(ui->tableView->actions()); + 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) { @@ -2685,11 +2228,11 @@ void MainWindow::dropEvent(QDropEvent *event) QString abspath = finfo.absoluteFilePath(); // Within the current workspace ? - if(abspath.indexOf(currentWorkspace)!=0) + if(abspath.indexOf(getCurrentWorkspace())!=0) continue; // skip // Remove workspace from full path - QString wkpath = abspath.right(abspath.length()-currentWorkspace.length()-1); + QString wkpath = abspath.right(abspath.length()-getCurrentWorkspace().length()-1); newfiles.append(wkpath); } @@ -2701,7 +2244,7 @@ void MainWindow::dropEvent(QDropEvent *event) return; // Do Add - runFossil(QStringList() << "add" << QuotePaths(newfiles) ); + fossil().addFiles(newfiles); refresh(); } @@ -2711,6 +2254,11 @@ void MainWindow::dropEvent(QDropEvent *event) //------------------------------------------------------------------------------ void MainWindow::setBusy(bool busy) { + if(busy) + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + else + QApplication::restoreOverrideCursor(); + abortShortcut->setEnabled(busy); bool enabled = !busy; ui->menuBar->setEnabled(enabled); @@ -2721,7 +2269,8 @@ void MainWindow::setBusy(bool busy) //------------------------------------------------------------------------------ void MainWindow::onAbort() { - abortOperation = true; + operationAborted = true; + fossil().abortOperation(); // FIXME: Rename this to something better, Operation Aborted log("
* "+tr("Terminated")+" *
", true); } @@ -2733,3 +2282,198 @@ void MainWindow::fullRefresh() // Select the Root of the tree to update the file view selectRootDir(); } + +//------------------------------------------------------------------------------ +void MainWindow::MainWinUICallback::logText(const QString& text, bool isHTML) +{ + Q_ASSERT(mainWindow); + mainWindow->log(text, isHTML); +} + +//------------------------------------------------------------------------------ +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(); +} + +//------------------------------------------------------------------------------ +void MainWindow::MainWinUICallback::updateProcess(const QString& text) +{ + Q_ASSERT(mainWindow); + mainWindow->ui->statusBar->showMessage(text); + QCoreApplication::processEvents(); +} + +//------------------------------------------------------------------------------ +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(); +} + diff --git a/src/MainWindow.h b/src/MainWindow.h index 4d4208c..dc7eb64 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -2,114 +2,15 @@ #define MAINWINDOW_H #include -#include #include -#include -#include -#include -#include #include -#include -#include "SettingsDialog.h" +#include "Settings.h" +#include "Workspace.h" namespace Ui { class MainWindow; } - -class QStringList; - -////////////////////////////////////////////////////////////////////////// -// RepoFile -////////////////////////////////////////////////////////////////////////// -struct RepoFile -{ - enum EntryType - { - TYPE_UNKNOWN = 1<<0, - TYPE_UNCHANGED = 1<<1, - TYPE_EDITTED = 1<<2, - 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_REPO = TYPE_UNCHANGED|TYPE_MODIFIED, - TYPE_ALL = TYPE_UNKNOWN|TYPE_REPO - }; - - RepoFile(QFileInfo &info, EntryType type, const QString &repoPath) - { - FileInfo = info; - Type = type; - FilePath = getRelativeFilename(repoPath); - Path = FileInfo.absolutePath(); - - // Strip the workspace path from the path - Q_ASSERT(Path.indexOf(repoPath)==0); - Path = Path.mid(repoPath.length()+1); - } - - bool isType(EntryType t) const - { - return Type == t; - } - - void setType(EntryType t) - { - Type = t; - } - - EntryType getType() const - { - return Type; - } - - QFileInfo getFileInfo() const - { - return FileInfo; - } - - bool isRepo() const - { - return Type == TYPE_UNCHANGED || Type == TYPE_EDITTED; - } - - const QString &getFilePath() const - { - return FilePath; - } - - QString getFilename() const - { - return FileInfo.fileName(); - } - - const QString &getPath() const - { - return Path; - } - - QString getRelativeFilename(const QString &path) - { - QString abs_base_dir = QDir(path).absolutePath(); - - QString relative = FileInfo.absoluteFilePath(); - int index = relative.indexOf(abs_base_dir); - if(index<0) - return QString(""); - - return relative.right(relative.length() - abs_base_dir.length()-1); - } - -private: - QFileInfo FileInfo; - EntryType Type; - QString FilePath; - QString Path; -}; - ////////////////////////////////////////////////////////////////////////// // MainWindow ////////////////////////////////////////////////////////////////////////// @@ -120,38 +21,26 @@ class MainWindow : public QMainWindow public: explicit MainWindow(Settings &_settings, QWidget *parent = 0, QString *workspacePath = 0); ~MainWindow(); - bool diffFile(QString repoFile); + bool diffFile(const QString& repoFile); void fullRefresh(); -private: - typedef QSet stringset_t; - enum RunFlags - { - 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 - }; - private: bool refresh(); void scanWorkspace(); - bool runFossil(const QStringList &args, QStringList *output=0, int runFlags=RUNFLAGS_NONE); - bool runFossilRaw(const QStringList &args, QStringList *output=0, int *exitCode=0, int runFlags=RUNFLAGS_NONE); 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 { return fossilUI.state() == QProcess::Running; } - void getSelectionFilenames(QStringList &filenames, int includeMask=RepoFile::TYPE_ALL, bool allIfEmpty=false); - void getFileViewSelection(QStringList &filenames, int includeMask=RepoFile::TYPE_ALL, bool allIfEmpty=false); - void getDirViewSelection(QStringList &filenames, int includeMask=RepoFile::TYPE_ALL, bool allIfEmpty=false); - void getStashViewSelection(QStringList &stashNames, bool allIfEmpty=false); + 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 getSelectionStashes(QStringList &stashNames); void getSelectionPaths(stringset_t &paths); - void getAllFilenames(QStringList &filenames, int includeMask=RepoFile::TYPE_ALL); + void getAllFilenames(QStringList &filenames, int includeMask=WorkspaceFile::TYPE_ALL); bool startUI(); void stopUI(); void enableActions(bool on); @@ -159,13 +48,11 @@ private: void rebuildRecent(); bool openWorkspace(const QString &path); void loadFossilSettings(); - QString getFossilPath(); - QString getFossilHttpAddress(); - bool scanDirectory(QFileInfoList &entries, const QString& dirPath, const QString &baseDir, const QString ignoreSpec, const bool& abort); - 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); @@ -173,15 +60,6 @@ private: virtual QMenu *createPopupMenu(); const QIcon& getInternalIcon(const char *name); - enum RepoStatus - { - REPO_OK, - REPO_NOT_FOUND, - REPO_OLD_SCHEMA - }; - - RepoStatus getRepoStatus(); - enum ViewMode { VIEWMODE_LIST, @@ -192,9 +70,11 @@ private slots: // Manual slots. // Use a different naming scheme to prevent warnings from Qt's automatic signaling void onOpenRecent(); - void onTreeViewSelectionChanged(const class QItemSelection &selected, const class QItemSelection &deselected); + 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(); @@ -204,8 +84,8 @@ private slots: void on_actionTimeline_triggered(); void on_actionHistory_triggered(); void on_actionClearLog_triggered(); - void on_tableView_doubleClicked(const QModelIndex &index); - void on_treeView_doubleClicked(const QModelIndex &index); + void on_fileTableView_doubleClicked(const QModelIndex &index); + void on_workspaceTreeView_doubleClicked(const QModelIndex &index); void on_actionOpenFile_triggered(); void on_actionPush_triggered(); void on_actionPull_triggered(); @@ -219,26 +99,57 @@ private slots: 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_tableView_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: + MainWinUICallback() : mainWindow(0) + {} + + void init(class MainWindow *mainWindow) + { + this->mainWindow = mainWindow; + } + + virtual void logText(const QString& text, bool isHTML); + virtual void beginProcess(const QString& text); + virtual void updateProcess(const QString& text); + virtual void endProcess(); + virtual QMessageBox::StandardButton Query(const QString &title, const QString &query, QMessageBox::StandardButtons buttons); + + + private: + class MainWindow *mainWindow; + }; + + friend class MainWinUICallback; + enum { MAX_RECENT=5 @@ -249,30 +160,36 @@ private: Ui::MainWindow *ui; QFileIconProvider iconProvider; icon_map_t iconCache; - QStandardItemModel repoFileModel; - QStandardItemModel repoDirModel; - QStandardItemModel repoStashModel; - QProcess fossilUI; class QAction *recentWorkspaceActs[MAX_RECENT]; class QProgressBar *progressBar; + class QLabel *lblRevision; + class QLabel *lblTags; class QShortcut *abortShortcut; - bool abortOperation; + 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(); } + const Fossil & fossil() const { return workspace.fossil(); } Settings &settings; - QString projectName; - QString repositoryFile; QStringList workspaceHistory; - QString currentWorkspace; - ViewMode viewMode; - stringset_t selectedDirs; // The directory selected in the tree - // Repository State - typedef QList filelist_t; - typedef QMap filemap_t; - typedef QMap stashmap_t; - filemap_t workspaceFiles; - stringset_t pathSet; - stashmap_t stashMap; + MainWinUICallback uiCallback; + + ViewMode viewMode; }; #endif // MAINWINDOW_H diff --git a/src/RevisionDialog.cpp b/src/RevisionDialog.cpp new file mode 100644 index 0000000..0f2ca8b --- /dev/null +++ b/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; +} diff --git a/src/RevisionDialog.h b/src/RevisionDialog.h new file mode 100644 index 0000000..42f678a --- /dev/null +++ b/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 diff --git a/src/SearchBox.cpp b/src/SearchBox.cpp new file mode 100644 index 0000000..0cbf495 --- /dev/null +++ b/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); +} + diff --git a/src/SearchBox.h b/src/SearchBox.h new file mode 100644 index 0000000..53b7487 --- /dev/null +++ b/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 diff --git a/src/Settings.cpp b/src/Settings.cpp new file mode 100644 index 0000000..95ce3cf --- /dev/null +++ b/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(); +} diff --git a/src/Settings.h b/src/Settings.h new file mode 100644 index 0000000..f0535ee --- /dev/null +++ b/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 diff --git a/src/SettingsDialog.cpp b/src/SettingsDialog.cpp index 3cf5810..59b86d7 100644 --- a/src/SettingsDialog.cpp +++ b/src/SettingsDialog.cpp @@ -1,37 +1,8 @@ #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) : @@ -54,7 +25,6 @@ SettingsDialog::SettingsDialog(QWidget *parent, Settings &_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) @@ -66,15 +36,6 @@ SettingsDialog::SettingsDialog(QWidget *parent, Settings &_settings) : 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()); } //----------------------------------------------------------------------------- @@ -97,7 +58,6 @@ void SettingsDialog::on_buttonBox_accepted() 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(); @@ -108,15 +68,6 @@ void SettingsDialog::on_buttonBox_accepted() 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(); } @@ -128,22 +79,6 @@ void SettingsDialog::on_btnSelectFossil_clicked() 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() { @@ -186,120 +121,3 @@ QString SettingsDialog::LangNameToId(const QString &name) 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(); -} diff --git a/src/SettingsDialog.h b/src/SettingsDialog.h index b57ba3b..418f2ea 100644 --- a/src/SettingsDialog.h +++ b/src/SettingsDialog.h @@ -2,82 +2,12 @@ #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 @@ -92,12 +22,9 @@ public: 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(); diff --git a/src/Utils.cpp b/src/Utils.cpp index 18ccce2..4dedf39 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -1,6 +1,7 @@ #include "Utils.h" #include #include +#include /////////////////////////////////////////////////////////////////////////////// QMessageBox::StandardButton DialogQuery(QWidget *parent, const QString &title, const QString &query, QMessageBox::StandardButtons buttons) @@ -14,6 +15,42 @@ QMessageBox::StandardButton DialogQuery(QWidget *parent, const QString &title, c return res; } +//----------------------------------------------------------------------------- +QString QuotePath(const QString &path) +{ + return path; +} + +//----------------------------------------------------------------------------- +QStringList QuotePaths(const QStringList &paths) +{ + QStringList res; + for(int i=0; i @@ -158,7 +195,7 @@ bool ShowExplorerMenu(HWND hwnd, const QString &path, const QPoint &qpoint) // IShellFolder interface. // bool bResult = false; - + LPMALLOC pMalloc; if (!SUCCEEDED (SHGetMalloc (&pMalloc))) return bResult; @@ -291,3 +328,64 @@ bool ShowExplorerMenu(HWND hwnd, const QString &path, const QPoint &qpoint) #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" + 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 +class UICallback +{ +public: + virtual void logText(const QString &text, bool isHTML)=0; + virtual void beginProcess(const QString &text)=0; + virtual void updateProcess(const QString &text)=0; + virtual void endProcess()=0; + virtual QMessageBox::StandardButton Query(const QString &title, const QString &query, QMessageBox::StandardButtons buttons)=0; +}; + + +class ScopedStatus +{ +public: + ScopedStatus(UICallback *callback, const QString &text) : uiCallback(callback) + { + uiCallback->beginProcess(text); + } + + ~ScopedStatus() + { + uiCallback->endProcess(); + } + +private: + UICallback *uiCallback; +}; #endif // UTILS_H diff --git a/src/Workspace.cpp b/src/Workspace.cpp new file mode 100644 index 0000000..fb5c467 --- /dev/null +++ b/src/Workspace.cpp @@ -0,0 +1,218 @@ +#include "Workspace.h" +#include +#include "Utils.h" + +//----------------------------------------------------------------------------- +Workspace::~Workspace() +{ + clearState(); +} + +//------------------------------------------------------------------------------ +void Workspace::clearState() +{ + // Dispose RepoFiles + 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) +{ + QDir dir(dirPath); + + uiCallback.updateProcess(dirPath); + + QFileInfoList list = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot); + for (int i=0; ifileName(); + QString fullpath = it->absoluteFilePath(); + + // Skip fossil files + if(filename == FOSSIL_CHECKOUT1 || filename == FOSSIL_CHECKOUT2 || (!fossil().getRepositoryFile().isEmpty() && QFileInfo(fullpath) == QFileInfo(fossil().getRepositoryFile()))) + continue; + + WorkspaceFile *rf = new WorkspaceFile(*it, WorkspaceFile::TYPE_UNKNOWN, wkdir); + getFiles().insert(rf->getFilePath(), rf); + getPaths().insert(rf->getPath()); + } + } + uiCallback.endProcess(); + + uiCallback.beginProcess(QObject::tr("Updating...")); + + // Update Files and Directories + + for(QStringList::iterator line_it=res.begin(); line_it!=res.end(); ++line_it) + { + QString line = (*line_it).trimmed(); + if(line.length()==0) + continue; + + 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; + + if(status_text=="EDITED") + type = WorkspaceFile::TYPE_EDITTED; + else if(status_text=="ADDED") + type = WorkspaceFile::TYPE_ADDED; + else if(status_text=="DELETED") + { + type = WorkspaceFile::TYPE_DELETED; + add_missing = true; + } + else if(status_text=="MISSING") + { + type = WorkspaceFile::TYPE_MISSING; + add_missing = true; + } + else if(status_text=="RENAMED") + 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)) + { + getFiles().remove(fname); + continue; + } + else + add_missing = true; + + Workspace::filemap_t::iterator it = getFiles().find(fname); + + WorkspaceFile *rf = 0; + if(add_missing && it==getFiles().end()) + { + QFileInfo info(wkdir+QDir::separator()+fname); + rf = new WorkspaceFile(info, type, wkdir); + getFiles().insert(rf->getFilePath(), rf); + } + + if(!rf) + { + it = getFiles().find(fname); + Q_ASSERT(it!=getFiles().end()); + rf = *it; + } + + rf->setType(type); + + QString path = rf->getPath(); + getPaths().insert(path); + } + + // 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(); +} + diff --git a/src/Workspace.h b/src/Workspace.h new file mode 100644 index 0000000..68dfa2c --- /dev/null +++ b/src/Workspace.h @@ -0,0 +1,145 @@ +#ifndef WORKSPACE_H +#define WORKSPACE_H + +#include +#include +#include +#include +#include +#include "Utils.h" +#include "Fossil.h" + +////////////////////////////////////////////////////////////////////////// +// WorkspaceFile +////////////////////////////////////////////////////////////////////////// +struct WorkspaceFile +{ + enum Type + { + TYPE_UNKNOWN = 1<<0, + TYPE_UNCHANGED = 1<<1, + TYPE_EDITTED = 1<<2, + TYPE_ADDED = 1<<3, + TYPE_DELETED = 1<<4, + TYPE_MISSING = 1<<5, + TYPE_RENAMED = 1<<6, + TYPE_CONFLICTED = 1<<7, + 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) + { + FileInfo = info; + FileType = type; + FilePath = getRelativeFilename(repoPath); + Path = FileInfo.absolutePath(); + + // Strip the workspace path from the path + Q_ASSERT(Path.indexOf(repoPath)==0); + Path = Path.mid(repoPath.length()+1); + } + + bool isType(Type t) const + { + return FileType == t; + } + + void setType(Type t) + { + FileType = t; + } + + Type getType() const + { + return FileType; + } + + QFileInfo getFileInfo() const + { + return FileInfo; + } + + const QString &getFilePath() const + { + return FilePath; + } + + QString getFilename() const + { + return FileInfo.fileName(); + } + + const QString &getPath() const + { + return Path; + } + + QString getRelativeFilename(const QString &path) + { + QString abs_base_dir = QDir(path).absolutePath(); + + QString relative = FileInfo.absoluteFilePath(); + int index = relative.indexOf(abs_base_dir); + if(index<0) + return QString(""); + + return relative.right(relative.length() - abs_base_dir.length()-1); + } + +private: + QFileInfo FileInfo; + Type FileType; + QString FilePath; + QString Path; +}; + + +typedef QSet stringset_t; + +////////////////////////////////////////////////////////////////////////// +// Workspace +////////////////////////////////////////////////////////////////////////// + +class Workspace +{ +public: + ~Workspace(); + + typedef QList filelist_t; + typedef QMap filemap_t; + + void clearState(); + + Fossil & fossil() { return bridge; } + const Fossil & fossil() const { return bridge; } + + 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 &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 repoTreeModel; +}; + +#endif // WORKSPACE_H diff --git a/src/main.cpp b/src/main.cpp index 7688571..98cbd88 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,7 +5,7 @@ int main(int argc, char *argv[]) { QApplication app(argc, argv); app.setApplicationName("Fuel"); - app.setApplicationVersion("1.0.1"); + app.setApplicationVersion("2.0.0"); app.setOrganizationDomain("fuel-scm.org"); app.setOrganizationName("Fuel-SCM"); diff --git a/ui/BrowserWidget.ui b/ui/BrowserWidget.ui index c651f9f..6ef556c 100644 --- a/ui/BrowserWidget.ui +++ b/ui/BrowserWidget.ui @@ -14,7 +14,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -44,7 +53,7 @@ - :/icons/icons/Button Previous-01.png:/icons/icons/Button Previous-01.png + :/icons/icon-action-previous:/icons/icon-action-previous Back @@ -56,7 +65,7 @@ - :/icons/icons/Button Next-01.png:/icons/icons/Button Next-01.png + :/icons/icon-action-next:/icons/icon-action-next Forward @@ -68,7 +77,7 @@ - :/icons/icons/Button Refresh-01.png:/icons/icons/Button Refresh-01.png + :/icons/icon-action-refresh:/icons/icon-action-refresh Refresh @@ -80,7 +89,7 @@ - :/icons/icons/Button Close-01.png:/icons/icons/Button Close-01.png + :/icons/icon-action-stop:/icons/icon-action-stop Stop diff --git a/ui/CommitDialog.ui b/ui/CommitDialog.ui index da07022..0907856 100644 --- a/ui/CommitDialog.ui +++ b/ui/CommitDialog.ui @@ -10,7 +10,7 @@ 0 0 400 - 300 + 394 @@ -62,9 +62,72 @@ - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + New branch + + + + + + + + + + + + + + false + + + + + + + + + + false + + + + + + + Branch name + + + + + + + Private branch + + + + + + + + - + Revert stashed files diff --git a/ui/FslSettingsDialog.ui b/ui/FslSettingsDialog.ui new file mode 100644 index 0000000..db9ea78 --- /dev/null +++ b/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 + + + + + diff --git a/ui/MainWindow.ui b/ui/MainWindow.ui index 6652298..7774bde 100644 --- a/ui/MainWindow.ui +++ b/ui/MainWindow.ui @@ -18,7 +18,7 @@ - :/icons/icons/Battery-01.png:/icons/icons/Battery-01.png + :/icons/icon-application:/icons/icon-application true @@ -58,7 +58,7 @@ Qt::Horizontal - + 20 @@ -66,7 +66,7 @@ - Qt::ActionsContextMenu + Qt::CustomContextMenu QAbstractItemView::NoEditTriggers @@ -78,16 +78,13 @@ QAbstractItemView::SelectItems - true + false - - true - false - + 80 @@ -140,44 +137,6 @@ 30 - - - - 20 - 0 - - - - Qt::ActionsContextMenu - - - QAbstractItemView::NoEditTriggers - - - true - - - QAbstractItemView::SelectRows - - - false - - - true - - - false - - - false - - - true - - - false - - @@ -190,7 +149,7 @@ QTabWidget::South - 1 + 0 @@ -263,7 +222,7 @@ 0 0 865 - 22 + 23 @@ -295,13 +254,34 @@ - - + + + + + + &Workspace + + + + + + + + + + + + + + + + + @@ -343,8 +323,6 @@ - - @@ -357,10 +335,10 @@ - :/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 @@ -375,10 +353,10 @@ - :/icons/icons/Save-01.png:/icons/icons/Save-01.png + :/icons/icon-action-commit:/icons/icon-action-commit - Commit + &Commit Commit modifications @@ -393,7 +371,7 @@ - :/icons/icons/Document Copy-01.png:/icons/icons/Document Copy-01.png + :/icons/icon-item-diff:/icons/icon-item-diff Diff @@ -411,7 +389,7 @@ - :/icons/icons/File New-01.png:/icons/icons/File New-01.png + :/icons/icon-item-add:/icons/icon-item-add Add @@ -429,7 +407,7 @@ - :/icons/icons/File Delete-01.png:/icons/icons/File Delete-01.png + :/icons/icon-item-delete:/icons/icon-item-delete Delete @@ -447,7 +425,7 @@ - :/icons/icons/Document Blank-01.png:/icons/icons/Document Blank-01.png + :/icons/icon-action-repo-new:/icons/icon-action-repo-new &New... @@ -465,7 +443,7 @@ - :/icons/icons/My Documents-01.png:/icons/icons/My Documents-01.png + :/icons/icon-action-repo-open:/icons/icon-action-repo-open &Open... @@ -497,10 +475,10 @@ - :/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 @@ -509,10 +487,10 @@ - :/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 @@ -527,10 +505,10 @@ - :/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 @@ -545,7 +523,7 @@ - :/icons/icons/File Open-01.png:/icons/icons/File Open-01.png + :/icons/icon-item-rename:/icons/icon-item-rename Rename @@ -563,7 +541,7 @@ - :/icons/icons/Button Turn Off-01.png:/icons/icons/Button Turn Off-01.png + :/icons/icon-action-quit:/icons/icon-action-quit &Quit @@ -584,7 +562,7 @@ - :/icons/icons/File History-01.png:/icons/icons/File History-01.png + :/icons/icon-item-history:/icons/icon-item-history History @@ -605,7 +583,7 @@ - :/icons/icons/Network MAC-01.png:/icons/icons/Network MAC-01.png + :/icons/icon-webview:/icons/icon-webview Fossil UI @@ -620,7 +598,7 @@ - :/icons/icons/Document-Revert-icon.png:/icons/icons/Document-Revert-icon.png + :/icons/icon-item-revert:/icons/icon-item-revert Revert @@ -635,7 +613,7 @@ - :/icons/icons/Text Edit.png:/icons/icons/Text Edit.png + :/icons/icon-clear-log:/icons/icon-clear-log Clear Log @@ -650,7 +628,7 @@ - :/icons/icons/Clock-01.png:/icons/icons/Clock-01.png + :/icons/icon-action-timeline:/icons/icon-action-timeline Timeline @@ -665,7 +643,7 @@ - :/icons/icons/Document-01.png:/icons/icons/Document-01.png + :/icons/icon-item-file:/icons/icon-item-file Open File @@ -683,7 +661,7 @@ - :/icons/icons/Folder-01.png:/icons/icons/Folder-01.png + :/icons/icon-action-folder-explore:/icons/icon-action-folder-explore Open Containing @@ -701,10 +679,10 @@ - :/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 @@ -719,7 +697,7 @@ - :/icons/icons/Battery-01.png:/icons/icons/Battery-01.png + :/icons/icon-application:/icons/icon-application &About... @@ -734,13 +712,13 @@ - :/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 @@ -752,7 +730,7 @@ - :/icons/icons/Gear-01.png:/icons/icons/Gear-01.png + :/icons/icon-action-settings:/icons/icon-action-settings &Preferences... @@ -840,7 +818,7 @@ - :/icons/icons/Folder-01.png:/icons/icons/Folder-01.png + :/icons/icon-item-folder:/icons/icon-item-folder Open Folder @@ -855,7 +833,7 @@ - :/icons/icons/Folder Open-01.png:/icons/icons/Folder Open-01.png + :/icons/icon-action-folder-rename:/icons/icon-action-folder-rename Rename Folder @@ -867,13 +845,13 @@ 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 @@ -882,7 +860,7 @@ - :/icons/icons/Folder Open-01.png:/icons/icons/Folder Open-01.png + :/icons/icon-action-stash-apply:/icons/icon-action-stash-apply Apply Stash @@ -894,27 +872,10 @@ 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 @@ -923,12 +884,100 @@ - :/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 + + diff --git a/ui/RevisionDialog.ui b/ui/RevisionDialog.ui new file mode 100644 index 0000000..f2bb9a1 --- /dev/null +++ b/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 + + + + + diff --git a/ui/SettingsDialog.ui b/ui/SettingsDialog.ui index 0117ecc..0685355 100644 --- a/ui/SettingsDialog.ui +++ b/ui/SettingsDialog.ui @@ -10,396 +10,177 @@ 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 + + + +