# # # rename "src/model/Attributes.cpp" # to "src/model/GetAttributes.cpp" # # rename "src/model/Attributes.h" # to "src/model/GetAttributes.h" # # add_file "res/forms/add_edit_attribute.ui" # content [82503020fbedbad0c1879c82e4203aaed9db637a] # # add_file "res/forms/file_history.ui" # content [04c0cef841a0eee70161d76b12d6772a92238bc0] # # add_file "res/forms/unaccounted_renames.ui" # content [035b00da70ee362272c7fe50039ec41364a231ff] # # add_file "res/icons/arrow_down.png" # content [6390b8ce26bb5b11266833f57a57266df5667388] # # add_file "res/icons/blue_dot.png" # content [374a767d9c0e4543e105686b885ad71950941e1d] # # add_file "res/icons/green_dot.png" # content [9a806782c23117a5bebf10a1ca5f581e7aa13f7e] # # add_file "res/icons/red_dot.png" # content [137b7c7a3e8e311b7168f1bc6b3364d7f1865060] # # add_file "res/icons/yellow_dot.png" # content [d534547752c8546f91dfefa8b352b9eb5e59a87b] # # add_file "src/model/GetContentChanged.cpp" # content [fac748457f536fe8ae1e4022dfc4be9ae9e3a4eb] # # add_file "src/model/GetContentChanged.h" # content [2caf1a98c245f56c89c7036aaf0814338b7327f7] # # add_file "src/util/BasicIOWriter.cpp" # content [7965db7212e1d26f78c7923a3c8add0fd8dd46b7] # # add_file "src/util/BasicIOWriter.h" # content [052ca233a44fa5c872b699b5eee4fd4cc6187f1b] # # add_file "src/view/MacStartMenu.cpp" # content [4cf7133a0572dc78749e556696e812b213d1d0c5] # # add_file "src/view/MacStartMenu.h" # content [d98d19bcc23c5c867e4c9a8ecc9c3d850bfa9b8c] # # add_file "src/view/dialogs/AddEditAttribute.cpp" # content [1f9555c4103d227d66191031fea7b1e9d41462e7] # # add_file "src/view/dialogs/AddEditAttribute.h" # content [aea71c7ba22359058eaccfc8be8d112dd79bf231] # # add_file "src/view/dialogs/FileHistory.cpp" # content [86e856616418ed7875cbd92515116b406947abc4] # # add_file "src/view/dialogs/FileHistory.h" # content [ed76ac56e2b114a67196d38bae14c01b8b9fda63] # # add_file "src/view/dialogs/UnaccountedRenames.cpp" # content [938d5eac2b9884b18445e4eacde26c5d31dea0e8] # # add_file "src/view/dialogs/UnaccountedRenames.h" # content [0691bf54c8ff2d8606e88eb855613a4977ad8b9d] # # add_file "tests/MonotoneTest.h" # content [73986287a1873b2e72dbc62743a7cdd9ab317fb7] # # add_file "tests/StdioParserTest.h" # content [3974e240f162fc6cebbd6df97c3f36ed0440817d] # # patch "NEWS" # from [c5dfe130fb53d6553fc530e7e94140ffd99fe2c3] # to [949d2a577317be4215e33daa7157824f994577d8] # # patch "guitone.pro" # from [33545cda1a516aede1c2b494f1432818727048c9] # to [793a62ed84e3c859c3963721260c4e6fa379f1fa] # # patch "notes/IDEAS" # from [0f7d32bac1e3d7405a733c7548508969064efe21] # to [bdf47a7c58a2a0c0f183ea9ef59290a46d7dbc3c] # # patch "notes/RELEASE_CHECKLIST" # from [3dd539c8e9b77fb957571a2c4dcb641ce25a44f0] # to [78816f3f795e8651b5ac5fd7b3870b735c7d3bd2] # # patch "notes/TODO" # from [ea5eaa751fa11645c142bca08911a145ef87d4a5] # to [336f24bc9bc9f3914981275846cbd7b05ff7e19e] # # patch "res/forms/application_update.ui" # from [cfb6889da734fdfa20f68943c73d93983951ce5a] # to [7d2b3993e719ba0fea62c815d0cee25d6fd48562] # # patch "res/forms/changeset_browser.ui" # from [b305fba833874efa7b6b53773d8cea303c0366d0] # to [ac59737143d7aac046048608d14ed2dbdefd23b6] # # patch "res/forms/commit_revision.ui" # from [7743280f847be7c2fd842e55b24e48be78d2b6ab] # to [869cc6762e7b033d8186dc527e9d96ab5e77ff49] # # patch "res/forms/file_diff.ui" # from [7892257391ce4faa476cd26cb04e424ac91a4e2d] # to [9999a04c668f9a40b1a9b20f7ecd0812e8dc7cd5] # # patch "res/forms/key_management.ui" # from [edec01c656964ce19977a8305c380207fa222bff] # to [4913ed46e0685544cb5e3cf7cdf98b54aacd42ce] # # patch "res/forms/main_window.ui" # from [6c5b7e9f7df0fc48722a97cf9e48cbc51ea46e2d] # to [2a502c4c0a34fefa6c210f0f74f6e98a7ad6fe2b] # # patch "res/forms/manifest.ui" # from [b0411bd9c26399c10dd09a486cc6403b80af3f7c] # to [5b3e49e7bec6ba679c67b10ef1c9c0ee7e6799d2] # # patch "res/forms/preferences.ui" # from [4c7a832f0d210371eb68882a1f736ff150affdff] # to [9898dad2631df0a8a5cb6f06fc5055160b7e24d2] # # patch "res/forms/revision_diff.ui" # from [d4d25a844c0a4a141bc9ac01933d96371b7116c0] # to [56845d2399bd6d4a4e6936b65311cb63a599ba99] # # patch "res/forms/select_revision.ui" # from [c1cecd2c790c38b27a87df2bea6e99ca65579c49] # to [b901de21917e7ec4fa9b7ebfa3f1bb5a93bdcf47] # # patch "res/guitone-icon.svg" # from [f485e11fe3d6d8003fec375967c5375a4ecb9b2f] # to [00effff3ab338037f85f83a29ee1a41e1f6525b6] # # patch "res/guitone.qrc" # from [d68d97cabc3486a59beaaac87fdb9a936dc75ac3] # to [f79689930cf514724cb11d18e452cba44630c2b6] # # patch "res/i18n/guitone_de.ts" # from [7d2efd4ce19b2d6b7fe344dfebb06da025cc22c1] # to [368821342cae5393b64812ebc3addd7032346589] # # patch "src/Guitone.cpp" # from [cae5d0b8210527a4327743b7ea8c4993d4e7639a] # to [444b0af3793794d34e4657b2fea5e5e983d90ae2] # # patch "src/Guitone.h" # from [881dffddd5bdd9999f2ed36ecdcaa019de153cb4] # to [9c9143ee93f0633494e48c42e0ee0f64d6af46d6] # # patch "src/main.cpp" # from [e36e3f6a9adf493840944a11b718bf25abe079de] # to [b3c466a5103800420aa2b0cab6c9d1be4994f5c5] # # patch "src/model/Certs.cpp" # from [0158c7df5e847ce46b6b56557ea7647d5b423b52] # to [e4a81c6408c64be1a305aa5eb70842e589e34311] # # patch "src/model/ContentDiff.cpp" # from [af2b8ba1408305c6413d2b0900bc9f7d1e76cb3c] # to [330cf8f074e03c6fc73d6581933660f2e5ac411c] # # patch "src/model/ContentDiff.h" # from [91ee6ba1a90506ce0890d45c4f55d9b146b2d871] # to [9f0b45bde2c813734bf6f4a9a5c8cb88e629a41d] # # patch "src/model/GetAttributes.cpp" # from [2ea5d72737c95823852fbf7038235b8af7349d49] # to [c1f8ccabf73da0b5f91634619197f0adbfeb31de] # # patch "src/model/GetAttributes.h" # from [a4d4844a547daa12380f96da44609c627e859b25] # to [f9f06f87a1177277bce55b69800a14df55fffbd3] # # patch "src/model/GetFile.cpp" # from [5151a9eccdcf6f92a140dc662d5c845f79484c34] # to [54bfdc61b73d50418793fca0b86bc05f434ee311] # # patch "src/model/GetFile.h" # from [2542dad0537061ec09b64b453ac07a972788b1c6] # to [341b069c5778068d50bbf01cd34557a04b639db3] # # patch "src/model/GetRevision.cpp" # from [a953b589dc8091738c22564d6c18ff070eb48211] # to [1ca82e3b9ba7a2956fc60cff0e976c0ec1e681e2] # # patch "src/model/GetRevision.h" # from [0308fe66ff1749162131c693e28efe7497579d55] # to [9c2675f10e96cf957979a04a2df71ae989d1d6c6] # # patch "src/model/Inventory.cpp" # from [fe03f18135e1c1a39e069ea74b3f6f36028156e5] # to [38d53b03b3303315ba61b72f5241ffa81e24f8f3] # # patch "src/model/Inventory.h" # from [e9d25b6dda9dcb6b3678cb3699da93695f4ac1ca] # to [c9834d77c69b3b9d7c6dc35a5a8bc6118a5b01ec] # # patch "src/model/Keys.cpp" # from [9d8302773f28fa012ce8550b4afc166f5318790b] # to [4fc764b9241d0fcdd8745c96a32dc7504ed26565] # # patch "src/model/Manifest.cpp" # from [2fbdb523819234d538fa763d4affeb9d81a9af6f] # to [e4700d858ea55077620324ec7c97cdb8286a876a] # # patch "src/model/Tags.cpp" # from [031fb14119fc2c97d0eee5a32b46e0acff4fbf7b] # to [f0fab0f388865ab22baea3acf6e5d98c13835b99] # # patch "src/monotone/FileExporter.cpp" # from [69943651783b092d180d1b8c350f1665bed145e4] # to [fe0cd260a3d9c57b64a3c0c1e2de197fe77b531f] # # patch "src/monotone/Monotone.cpp" # from [8d3d1bfe62b2dcb9f94f5f7f676e85bca5b16bbd] # to [21de72375ce610dba385952d2ba8edb939b42810] # # patch "src/monotone/Monotone.h" # from [eb86d372a5790b81c4e17e6c8a66ce7ec5cc90c7] # to [b8a441bb1a91236c16fa397e87751a0a2101da59] # # patch "src/monotone/MonotoneDelegate.cpp" # from [e662336f12dfd870e514d14b05d0ba5c028eaf48] # to [0b1b0ebfaa468cbf89d3ef32a54e9791405b86f5] # # patch "src/monotone/MonotoneDelegate.h" # from [907a718c030f96ed3c2951e5b5b1442b72b6891a] # to [6971e5c0d79d50fc766f84fb395cf648da80c8e5] # # patch "src/monotone/WorkspaceCommitter.cpp" # from [976aad817f2bb63cbb6c0220e0f30cf6bd8f6517] # to [2c27ad30e62fd2782321ae897406b9f095ec5c53] # # patch "src/monotone/WorkspaceCommitter.h" # from [df5912a27f8f00ff7c954bb50dfe354125a855fc] # to [360938d22820d95f116691a962b6cb92ce9bc771] # # patch "src/util/AbstractParser.h" # from [af473981a098c6223e3d11183e1470ce86d8befe] # to [5d83ade859fbf057e5326f2bdcf8b0f6560a5be2] # # patch "src/util/BasicIOParser.cpp" # from [41c485e6ae2759a0084019bf94663f20b0730949] # to [f11c59b9074eee097cb97feea7f93bdd146ccb0d] # # patch "src/util/BasicIOParser.h" # from [2abdc40c948b2678ceeebf976e5424076036a83d] # to [b96df0f29dd64ac740c6e8a7f34c080406d3837e] # # patch "src/util/CocoaUtil.h" # from [8424a0c7f877f0167271bb2386fd00472a32f551] # to [7bab17bbe14218ea6e1dc33bbca110c0cc13a248] # # patch "src/util/CocoaUtil.mm" # from [af7058410a39e0fa1f7fdb9d1ff5019261fd0960] # to [5fe5b302f4e7198a04700856bab989b3b7e2f44c] # # patch "src/util/DebugLog.cpp" # from [ebbb4ff026ff490147e9c81f53f3c1b23c82febf] # to [513339e2eb09994edeed4ad8ed3d98b9a6101c81] # # patch "src/util/DebugLog.h" # from [15ae25ff50330f9a76933a81f344875cd72554d8] # to [94f2cac41d2381a8ab113dc5adb29131844beaf4] # # patch "src/util/DiffParser.cpp" # from [e98a52a2c7bee64b13376f3cf71b47629a213a7d] # to [4a0256295820e64a621bee2c82c34357ac711b91] # # patch "src/util/StdioParser.h" # from [8488d8ccabd54eef76d773c0ccaf91ac47d95d3d] # to [ed0742f484843b709c946a2c030cca934432335d] # # patch "src/view/AttributesView.cpp" # from [622bd8e562abeaa8299ecac4d3a725a0f8eb73ae] # to [e4b0b75ba4c9abd6fa8efa2c6def8a4f2ae6dc96] # # patch "src/view/AttributesView.h" # from [6331d48f84cd19a9389ea33efd8e11278867b3eb] # to [919369059dde5efb8b1e8b0c0d1e25e49dfc6825] # # patch "src/view/InventoryView.cpp" # from [bb051af5d94c6e995a1b33658b5d529b87b2df95] # to [441ff9c1a21693b33b00efe2894d0f50425f6b3c] # # patch "src/view/InventoryView.h" # from [0ad2ef23cc24538fcd3fd656b712f7b6dca17e3e] # to [fd0d9c5eb87a628057d22b2a41426dcbbe84ca03] # # patch "src/view/MainWindow.cpp" # from [c485c30152a7f43a73d96c33e13054771c5f0250] # to [c4b6cf1f128df54b18ba2a5b8ca2a2fddc0231aa] # # patch "src/view/MainWindow.h" # from [7ea22986b3ba2005db4b604c592a91365ba78122] # to [49a87144c3f5ef2d99f1ca0acc4acb11e21a402c] # # patch "src/view/TreeView.cpp" # from [5b4f86399682825dd48a24084136979464fd5c69] # to [3707234163333d91629f88fd1c84576610f942f0] # # patch "src/view/dialogs/ApplicationUpdate.cpp" # from [7c2ecb7a44dcd75bd5d53e94efe173113df90547] # to [5b6a164ff54ef8fabc95a3769c2b3c1fd2a878b3] # # patch "src/view/dialogs/CommitRevision.cpp" # from [ffe771b0f74202063686e3d30bcff6f6b9d0636a] # to [bba8d6475e14a2003cad91417b9bdd901ac15f55] # # patch "src/view/dialogs/Dialog.cpp" # from [926c311a65c0a2eeea6cae03256a56b8adbb7087] # to [4a9329c127de291fc2e8c236ee2ae3f97344bdfa] # # patch "src/view/dialogs/FileDiff.cpp" # from [3dae40899816900fb362793fef3deda6d2809e5c] # to [0df18f51a4020c5a2a36495b8da49fc5348c53c5] # # patch "src/view/dialogs/FileDiff.h" # from [dcba92805d020b9d0d83de903f27abda0a5a7c74] # to [c254fa1cdec0a6fe6545adcf784e5cbcb5ca1573] # # patch "src/view/dialogs/Preferences.cpp" # from [c34d5f6dce4113366c6d807b1b0a4bb97d3350b9] # to [4f3d46e215748754366bafc460894a91b6febc34] # # patch "src/vocab.h" # from [dbf6b8075bfee39d1c9494bf7fbc58b842904213] # to [afe123e8771f70b90c89dc2a9cddeb6206f81142] # # patch "tests/test.cpp" # from [1bc1c23455d91468e22f229dffa8fcee9277a8a9] # to [8c345925e352aea61e5f51a0b2ad01fb6475c282] # # patch "tests/test.pro" # from [5005bf499b91c8cf6e51e3c74915ee1d9069a312] # to [b437a3ca1b89faa23f2665324caa1a2f121184a3] # # set "res/icons/arrow_down.png" # attr "mtn:manual_merge" # value "true" # # set "res/icons/blue_dot.png" # attr "mtn:manual_merge" # value "true" # # set "res/icons/green_dot.png" # attr "mtn:manual_merge" # value "true" # # set "res/icons/red_dot.png" # attr "mtn:manual_merge" # value "true" # # set "res/icons/yellow_dot.png" # attr "mtn:manual_merge" # value "true" # ============================================================ --- res/forms/add_edit_attribute.ui 82503020fbedbad0c1879c82e4203aaed9db637a +++ res/forms/add_edit_attribute.ui 82503020fbedbad0c1879c82e4203aaed9db637a @@ -0,0 +1,150 @@ + + AddEditAttributeDialog + + + + 0 + 0 + 422 + 136 + + + + Add / edit an attribute + + + :/icons/guitone.png + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + 0 + + + 6 + + + + + 0 + + + 6 + + + + + Key + + + + + + + Value + + + + + + + + + 0 + + + 6 + + + + + true + + + + + + + + 3 + 0 + 0 + 0 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + false + + + + + + + + + + + + + buttonBox + rejected() + AddEditAttributeDialog + reject() + + + 271 + 109 + + + 236 + 122 + + + + + buttonBox + accepted() + AddEditAttributeDialog + accept() + + + 353 + 107 + + + 379 + 127 + + + + + ============================================================ --- res/forms/file_history.ui 04c0cef841a0eee70161d76b12d6772a92238bc0 +++ res/forms/file_history.ui 04c0cef841a0eee70161d76b12d6772a92238bc0 @@ -0,0 +1,206 @@ + + FileHistoryDialog + + + + 0 + 0 + 611 + 411 + + + + History of %1 + + + :/icons/guitone.png + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + Qt::Vertical + + + + + 0 + + + 6 + + + + + true + + + QAbstractItemView::SingleSelection + + + false + + + false + + + + + + + 0 + + + 6 + + + + + false + + + Select as first revision + + + + + + + false + + + Select as second revision + + + + + + + Qt::Horizontal + + + + 538 + 20 + + + + + + + + false + + + Show differences + + + + + + + + + + true + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + false + + + false + + + + + + + + 0 + + + 6 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + true + + + + + + + + + + + + Splitter + QSplitter +
Splitter.h
+
+ + TreeView + QTreeView +
TreeView.h
+
+
+ + + + + + closeButton + clicked() + FileHistoryDialog + close() + + + 229 + 252 + + + 199 + 149 + + + + +
============================================================ --- res/forms/unaccounted_renames.ui 035b00da70ee362272c7fe50039ec41364a231ff +++ res/forms/unaccounted_renames.ui 035b00da70ee362272c7fe50039ec41364a231ff @@ -0,0 +1,104 @@ + + UnaccountedRenamesDialog + + + + 0 + 0 + 454 + 316 + + + + Unaccounted renames + + + :/icons/guitone.png + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + + + + 0 + + + 6 + + + + + false + + + Perform checked renames + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + true + + + + + + + + + + + + + + + closeButton + clicked() + UnaccountedRenamesDialog + accept() + + + 399 + 272 + + + 252 + 154 + + + + + ============================================================ # res/icons/arrow_down.png is binary ============================================================ # res/icons/blue_dot.png is binary ============================================================ # res/icons/green_dot.png is binary ============================================================ # res/icons/red_dot.png is binary ============================================================ # res/icons/yellow_dot.png is binary ============================================================ --- src/model/GetContentChanged.cpp fac748457f536fe8ae1e4022dfc4be9ae9e3a4eb +++ src/model/GetContentChanged.cpp fac748457f536fe8ae1e4022dfc4be9ae9e3a4eb @@ -0,0 +1,334 @@ +/*************************************************************************** + * Copyright (C) 2007 by Thomas Keller * + * address@hidden * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "GetContentChanged.h" +#include "BasicIOParser.h" + +#include + +GetContentChanged::GetContentChanged(QObject *parent) + : QAbstractItemModel(parent) +{ + mtnDelegate = new MonotoneDelegate(this); +} + +GetContentChanged::~GetContentChanged() +{ + if (commandStack.size() > 0) + { + W("Unfinished commands."); + } + + revisions.clear(); + pathInRevision.clear(); + + delete mtnDelegate; +} + +// +// The basic flow is the following: +// +// [ workspace parent revision ] [ path in this revision ] +// \ / +// \ / +// \ / +// | +// V +// |---------------> [ get content changed ] +// | | +// | V +// | [ add the marked rev(s) to the list ] +// | | +// | V +// | [ get corresponding path for each marked rev ] +// | | +// | V +// |---- < ---- [ get parents of each marked rev ] -> stop if no parents +// | | +// | V +// |--- < --- [ get corresponding path for each parent ] -> stop if no path +// +bool GetContentChanged::readChanges(const QString & path) +{ + revisions.clear(); + pathInRevision.clear(); + + // reset the view + reset(); + + // find a starting point + // FIXME: we assume that the given path is part of this revision! + startRev = MonotoneDelegate::getBaseWorkspaceRevision(this); + I(!startRev.isNull()); + startPath = path; + + return queryContentChanged(startRev, startPath); +} + +bool GetContentChanged::queryContentChanged(const QString & rev, const QString & path) +{ + commandStack.enqueue(ContentChanged); + + QStringList cmd; + cmd << "get_content_changed" << rev << path; + return mtnDelegate->triggerCommand(cmd); +} + +bool GetContentChanged::queryParents(const QString & rev) +{ + commandStack.enqueue(Parents); + + QStringList cmd; + cmd << "parents" << rev; + return mtnDelegate->triggerCommand(cmd); +} + +bool GetContentChanged::queryCorrespondingPath(const QString & rev, const QString & path, const QString & par) +{ + commandStack.enqueue(CorrespondingPath); + + QStringList cmd; + cmd << "get_corresponding_path" << rev << path << par; + return mtnDelegate->triggerCommand(cmd); +} + + +void GetContentChanged::parseOutput() +{ + // this method is called when we either called get_content_changed, + // parents or get_corresponding_path + // to decide what to do next, we need to know what was triggered + // lately in the queue + Command current = commandStack.dequeue(); + + if (current == Parents) + { + if (AutomateCommand::data.isEmpty()) + { + reset(); + emit rootReached(); + return; + } + + QStringList parents = AutomateCommand::data.split( + '\n', QString::SkipEmptyParts + ); + + foreach (QString par, parents) + { + // if we checked this revision already, skip it + if (pathInRevision.contains(par)) + { + continue; + } + + revsForPathStack.enqueue(par); + I(queryCorrespondingPath(startRev, startPath, par)); + } + return; + } + + if (current == ContentChanged) + { + // since we're looking for a file in a certain revision + // _beforehand_ via get_corresponding_path, we should always + // get a stanza out here + BasicIOParser parser(AutomateCommand::data); + I(parser.parse()); + StanzaList stanzas = parser.getStanzas(); + foreach (Stanza st, stanzas) + { + I(st.size() == 1); + QString rev = st.at(0).hash; + I(!rev.isNull()); + + // if we have this particular revision already recorded, + // skip it + if (revisions.contains(rev)) continue; + + // append the revision to the ordered list + revisions.append(rev); + + // check if we already know the path in that revision + if (!pathInRevision.contains(rev)) + { + // add the revision to the stack of revs which need + // to get a valid path queried + revsForPathStack.enqueue(rev); + + // query for the corresponding path + I(queryCorrespondingPath(startRev, startPath, rev)); + } + + // query for the parents of this revision for the + // next round + I(queryParents(rev)); + } + return; + } + + if (current == CorrespondingPath) + { + I(revsForPathStack.size() > 0); + QString rev = revsForPathStack.dequeue(); + + if (AutomateCommand::data.isEmpty()) + { + // check if this is a marked node, if so, + // we have a serious problem (node is changed in this rev, + // but has no corresponding path) + if (revisions.contains(rev)) + { + I(false); + } + + // apparently this is another, not interesting (parent) node + // note that this can happen + reset(); + emit endOfLineReached(); + return; + } + + BasicIOParser parser(AutomateCommand::data); + I(parser.parse()); + StanzaList stanzas = parser.getStanzas(); + I(stanzas.size() == 1); + Stanza st = stanzas.at(0); + I(st.size() == 1); + StanzaEntry en = st.at(0); + I(en.sym == "file" && en.vals.size() == 1); + QString path = en.vals.at(0); + + // we're also inserting a lot of uninteresting (revision, path) + // tuples here, i.e. for every parent revision of a marked node, + // however the only list that counts is revisions + pathInRevision.insert(rev, path); + + // try to get the next marked node + I(queryContentChanged(rev, path)); + return; + } + + I(false); +} + +bool GetContentChanged::handleError(int retCode) +{ + if (retCode == 2) + { + I(commandStack.size() > 0); + + // FIXME: this is a big hack, since we assume that the only error 2 + // we can get here for this command basically reads like + // file "foo" doesn't exists in revision "bar" + // but yeah, localized string parsing is stupid as well + if (commandStack.head() == CorrespondingPath) + { + AutomateCommand::data.clear(); + parseOutput(); + return true; + } + } + + return false; +} + +int GetContentChanged::columnCount(const QModelIndex & parent) const +{ + return 2; +} + +QVariant GetContentChanged::data(const QModelIndex & index, int role) const +{ + if (!index.isValid()) + { + return QVariant(); + } + + int col = index.column(); + + if (role == Qt::FontRole && col == 0) + { + QFont font; + font.setStyleHint(QFont::Courier); + font.setFamily("Courier"); + return QVariant(font); + } + + if (role == Qt::DisplayRole) + { + int row = index.row(); + if (row >= revisions.size()) return QVariant(); + QString rev = revisions.at(row); + I(pathInRevision.contains(rev)); + + switch (col) + { + case 0: return QVariant(rev); + case 1: return QVariant(pathInRevision.value(rev)); + } + return QVariant(); + } + + return QVariant(); +} + +Qt::ItemFlags GetContentChanged::flags(const QModelIndex & index) const +{ + if (index.isValid()) + { + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } + return 0; +} + +QVariant GetContentChanged::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch (section) + { + case 0: return QVariant(tr("Revision ID")); + case 1: return QVariant(tr("Path in this revision")); + } + } + return QVariant(); +} + +int GetContentChanged::rowCount(const QModelIndex & parent) const +{ + return revisions.size(); +} + +QModelIndex GetContentChanged::index(int row, int column, const QModelIndex & parent) const +{ + if (!hasIndex(row, column, parent)) + { + return QModelIndex(); + } + + return createIndex(row, column, 0); +} + +QModelIndex GetContentChanged::parent(const QModelIndex & index) const +{ + return QModelIndex(); +} + ============================================================ --- src/model/GetContentChanged.h 2caf1a98c245f56c89c7036aaf0814338b7327f7 +++ src/model/GetContentChanged.h 2caf1a98c245f56c89c7036aaf0814338b7327f7 @@ -0,0 +1,83 @@ +/*************************************************************************** + * Copyright (C) 2007 by Thomas Keller * + * address@hidden * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef GETCONTENTCHANGED_H +#define GETCONTENTCHANGED_H + +#include "AutomateCommand.h" +#include "MonotoneDelegate.h" + +#include +#include +#include + +typedef QList RevisionList; + +class GetContentChanged : public QAbstractItemModel, public AutomateCommand +{ + Q_OBJECT +public: + GetContentChanged(QObject*); + virtual ~GetContentChanged(); + + // needed Qt Model methods + QVariant data(const QModelIndex&, int) const; + Qt::ItemFlags flags(const QModelIndex&) const; + QVariant headerData(int, Qt::Orientation, int) const; + QModelIndex index(int, int, const QModelIndex&) const; + QModelIndex parent(const QModelIndex&) const; + int rowCount(const QModelIndex&) const; + int columnCount(const QModelIndex&) const; + +public slots: + bool readChanges(const QString &); + +signals: + // is emitted each time the algorithm reaches the end of a + // particular development line for a specific file + void endOfLineReached(); + // is emitted if the root revision has been reached, + // which has per se no parent revisions + void rootReached(); + +private: + bool queryContentChanged(const QString &, const QString &); + bool queryParents(const QString &); + bool queryCorrespondingPath(const QString &, const QString &, const QString &); + + void parseOutput(); + bool handleError(int); + + RevisionList revisions; + MonotoneDelegate * mtnDelegate; + + enum Command { ContentChanged, CorrespondingPath, Parents }; + + QQueue commandStack; + QQueue revsForPathStack; + + QMap pathInRevision; + + QString startRev; + QString startPath; +}; + +#endif + ============================================================ --- src/util/BasicIOWriter.cpp 7965db7212e1d26f78c7923a3c8add0fd8dd46b7 +++ src/util/BasicIOWriter.cpp 7965db7212e1d26f78c7923a3c8add0fd8dd46b7 @@ -0,0 +1,80 @@ +/*************************************************************************** +* Copyright (C) 2006 by Thomas Keller * +* address@hidden * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program; if not, write to the * +* Free Software Foundation, Inc., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include "BasicIOWriter.h" + +BasicIOWriter::BasicIOWriter(const StanzaList & st) : stanzas(st) {} + +BasicIOWriter::~BasicIOWriter() {} + +QString BasicIOWriter::write() +{ + QString out; + foreach (Stanza st, stanzas) + { + out.append(writeStanza(st)); + } + return out; +} + +QString BasicIOWriter::writeStanza(const Stanza & stanza) +{ + QString out; + foreach (StanzaEntry en, stanza) + { + // ensure that not both, a hash and a value list, are given + I(!(!en.hash.isNull() && en.vals.size() > 0)); + + if (!en.hash.isNull()) + { + out.append(writeHashLine(en)); + } + else + { + out.append(writeValueLine(en)); + } + } + out.append("\n"); + return out; +} + +QString BasicIOWriter::writeHashLine(const StanzaEntry & entry) +{ + return QString("%1 [%2]\n").arg(entry.sym).arg(entry.hash); +} + +QString BasicIOWriter::writeValueLine(const StanzaEntry & entry) +{ + QString vals; + foreach (QString val, entry.vals) + { + vals.append(QString(" \"%1\"").arg(escape(val))); + } + return QString("%1%2\n").arg(entry.sym).arg(vals); +} + +QString BasicIOWriter::escape(const QString & in) +{ + QString out(in); + out.replace("\\", "\\\\"); + out.replace("\"", "\\\""); + return out; +} + ============================================================ --- src/util/BasicIOWriter.h 052ca233a44fa5c872b699b5eee4fd4cc6187f1b +++ src/util/BasicIOWriter.h 052ca233a44fa5c872b699b5eee4fd4cc6187f1b @@ -0,0 +1,45 @@ +/*************************************************************************** +* Copyright (C) 2007 by Thomas Keller * +* address@hidden * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program; if not, write to the * +* Free Software Foundation, Inc., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef BASICIO_WRITER_H +#define BASICIO_WRITER_H + +#include "vocab.h" + +class BasicIOWriter +{ +public: + BasicIOWriter(const StanzaList &); + ~BasicIOWriter(); + + QString write(); + + +private: + QString writeStanza(const Stanza &); + QString writeHashLine(const StanzaEntry &); + QString writeValueLine(const StanzaEntry &); + QString escape(const QString &); + + StanzaList stanzas; +}; + +#endif + ============================================================ --- src/view/MacStartMenu.cpp 4cf7133a0572dc78749e556696e812b213d1d0c5 +++ src/view/MacStartMenu.cpp 4cf7133a0572dc78749e556696e812b213d1d0c5 @@ -0,0 +1,205 @@ +/*************************************************************************** + * Copyright (C) 2007 by Thomas Keller * + * address@hidden * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "MacStartMenu.h" +#include "About.h" +#include "Preferences.h" +#include "CocoaUtil.h" +#include "Settings.h" + +#include +#include + +// +// FIXME: This is pretty much a plain copy of ui_main_window.h and +// MainWindow.cpp - if we keep this, then maybe this should be generalized +// i.e. into a "CommonMenuBar" class or something alike +// +MacStartMenu::MacStartMenu() : QMenuBar(0) +{ + actionOpen_Workspace = new QAction(this); + actionOpen_Workspace->setObjectName(QString::fromUtf8("actionOpen_Workspace")); + actionNo_recent_workspaces_found = new QAction(this); + actionNo_recent_workspaces_found->setObjectName(QString::fromUtf8("actionNo_recent_workspaces_found")); + actionPreferences = new QAction(this); + actionPreferences->setObjectName(QString::fromUtf8("actionPreferences")); + actionPreferences->setMenuRole(QAction::PreferencesRole); + actionAbout_Qt = new QAction(this); + actionAbout_Qt->setObjectName(QString::fromUtf8("actionAbout_Qt")); + actionAbout_Qt->setMenuRole(QAction::AboutQtRole); + actionAbout_guitone = new QAction(this); + actionAbout_guitone->setObjectName(QString::fromUtf8("actionAbout_guitone")); + actionAbout_guitone->setMenuRole(QAction::AboutRole); + actionOpen_Database = new QAction(this); + actionOpen_Database->setObjectName(QString::fromUtf8("actionOpen_Database")); + actionNo_previous_databases_available = new QAction(this); + actionNo_previous_databases_available->setObjectName(QString::fromUtf8("actionNo_previous_databases_available")); + actionCheck_for_updates = new QAction(this); + actionCheck_for_updates->setObjectName(QString::fromUtf8("actionCheck_for_updates")); + + menuFile = new QMenu(this); + menuFile->setObjectName(QString::fromUtf8("menuFile")); + menuRecent_Databases = new QMenu(menuFile); + menuRecent_Databases->setObjectName(QString::fromUtf8("menuRecent_Databases")); + menuRecent_Workspaces = new QMenu(menuFile); + menuRecent_Workspaces->setObjectName(QString::fromUtf8("menuRecent_Workspaces")); + + menuFile->addAction(actionOpen_Workspace); + menuFile->addAction(menuRecent_Workspaces->menuAction()); + menuFile->addSeparator(); + menuFile->addAction(actionOpen_Database); + menuFile->addAction(menuRecent_Databases->menuAction()); + menuFile->addSeparator(); + menuFile->addAction(actionPreferences); + menuFile->addAction(actionCheck_for_updates); + menuFile->addAction(actionAbout_Qt); + menuFile->addAction(actionAbout_guitone); + menuRecent_Databases->addAction(actionNo_previous_databases_available); + menuRecent_Workspaces->addAction(actionNo_recent_workspaces_found); + + // add the created menu to this menu bar + addAction(menuFile->menuAction()); + + // translate everything + actionOpen_Workspace->setText(tr("Open Workspace")); + actionOpen_Workspace->setShortcut(tr("Ctrl+O")); + actionNo_recent_workspaces_found->setText(tr("No previous workspaces available.")); + actionNo_recent_workspaces_found->setIconText(tr("No previous workspaces available.")); + actionNo_recent_workspaces_found->setToolTip(tr("No previous workspaces available.")); + actionPreferences->setText(tr("Preferences...")); + actionPreferences->setShortcut(tr("Ctrl+P")); + actionAbout_Qt->setText(tr("About Qt")); + actionAbout_guitone->setText(tr("About guitone")); + actionOpen_Database->setText(tr("Open Database")); + actionOpen_Database->setShortcut(tr("Ctrl+Shift+O")); + actionNo_previous_databases_available->setText(tr("No previous databases available.")); + actionCheck_for_updates->setText(tr("Check for updates")); + menuFile->setTitle(tr("File")); + menuRecent_Databases->setTitle(tr("Recent Databases")); + menuRecent_Workspaces->setTitle(tr("Recent Workspaces")); + + // fill in any recently loaded databases and workspaces + QStringList previousDb = Settings::getItemList("RecentDatabaseList"); + int elemCount = previousDb.size(); + if (elemCount > 0) + { + menuRecent_Databases->clear(); + + QAction *act; + for (int i = 0; i < elemCount; ++i) + { + act = menuRecent_Databases->addAction( + tr("&%1 %2").arg(i + 1).arg(previousDb[i]), + this, + SLOT(openRecentDatabase()) + ); + act->setData(previousDb[i]); + } + } + + QStringList previousWs = Settings::getItemList("RecentWorkspaceList"); + elemCount = previousWs.size(); + if (elemCount > 0) + { + menuRecent_Workspaces->clear(); + + QAction *act; + for (int i = 0; i < elemCount; ++i) + { + act = menuRecent_Workspaces->addAction( + tr("&%1 %2").arg(i + 1).arg(previousWs[i]), + this, + SLOT(openRecentWorkspace()) + ); + act->setData(previousWs[i]); + } + } + + // auto-connect all signals/slots + QMetaObject::connectSlotsByName(this); +} + +MacStartMenu::~MacStartMenu() {} + +void MacStartMenu::on_actionPreferences_triggered() +{ + Preferences dialog(this); + dialog.exec(); +} + +void MacStartMenu::on_actionAbout_guitone_triggered() +{ + About dialog(this); + dialog.exec(); +} + +void MacStartMenu::on_actionAbout_Qt_triggered() +{ + qApp->aboutQt(); +} + +void MacStartMenu::on_actionCheck_for_updates_triggered() +{ + CocoaUtil::checkForUpdates(); +} + +void MacStartMenu::on_actionOpen_Workspace_triggered() +{ + QString fn = QFileDialog::getExistingDirectory(0, tr("Select your workspace...")); + + if (!fn.isEmpty()) + { + emit loadWorkspace(fn); + } +} + +void MacStartMenu::on_actionOpen_Database_triggered() +{ + QString fn = QFileDialog::getOpenFileName( + 0, + tr("Select your database..."), + QString(), + tr("monotone Databases (*.mtn *.db)") + ); + + if (!fn.isEmpty()) + { + emit loadDatabase(fn); + } +} + +void MacStartMenu::openRecentWorkspace() +{ + QAction *action = qobject_cast(sender()); + if (action) + { + emit loadWorkspace(action->data().toString()); + } +} + +void MacStartMenu::openRecentDatabase() +{ + QAction *action = qobject_cast(sender()); + if (action) + { + emit loadDatabase(action->data().toString()); + } +} + ============================================================ --- src/view/MacStartMenu.h d98d19bcc23c5c867e4c9a8ecc9c3d850bfa9b8c +++ src/view/MacStartMenu.h d98d19bcc23c5c867e4c9a8ecc9c3d850bfa9b8c @@ -0,0 +1,63 @@ +/*************************************************************************** + * Copyright (C) 2007 by Thomas Keller * + * address@hidden * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef MAC_START_MENU_H +#define MAC_START_MENU_H + +#include + +class MacStartMenu : public QMenuBar +{ + Q_OBJECT + +public: + MacStartMenu(); + ~MacStartMenu(); + +signals: + void loadWorkspace(const QString &); + void loadDatabase(const QString &); + +public: + QAction * actionOpen_Workspace; + QAction * actionNo_recent_workspaces_found; + QAction * actionPreferences; + QAction * actionAbout_Qt; + QAction * actionAbout_guitone; + QAction * actionOpen_Database; + QAction * actionNo_previous_databases_available; + QMenu * menuFile; + QMenu * menuRecent_Databases; + QMenu * menuRecent_Workspaces; + QAction * actionCheck_for_updates; + +private slots: + void on_actionOpen_Workspace_triggered(); + void on_actionOpen_Database_triggered(); + void on_actionPreferences_triggered(); + void on_actionAbout_guitone_triggered(); + void on_actionAbout_Qt_triggered(); + void on_actionCheck_for_updates_triggered(); + void openRecentDatabase(); + void openRecentWorkspace(); +}; + +#endif + ============================================================ --- src/view/dialogs/AddEditAttribute.cpp 1f9555c4103d227d66191031fea7b1e9d41462e7 +++ src/view/dialogs/AddEditAttribute.cpp 1f9555c4103d227d66191031fea7b1e9d41462e7 @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2007 by Thomas Keller * + * address@hidden * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "AddEditAttribute.h" + +AddEditAttribute::AddEditAttribute( + QWidget * parent, const QString & key, const QString & value +) : Dialog(parent) +{ + setupUi(this); + Dialog::init(); + + // these are added here and not in designer to prevent their + // automatic localization through uic + attrKey->insertItem(0, "mtn:execute"); + attrKey->insertItem(1, "mtn:manual_merge"); + + int keyPos = attrKey->findText(key); + if (keyPos == -1) + { + attrKey->insertItem(0, key); + keyPos = 0; + } + + attrKey->setCurrentIndex(keyPos); + attrValue->setText(value); +} + +AddEditAttribute::~AddEditAttribute() {} + ============================================================ --- src/view/dialogs/AddEditAttribute.h aea71c7ba22359058eaccfc8be8d112dd79bf231 +++ src/view/dialogs/AddEditAttribute.h aea71c7ba22359058eaccfc8be8d112dd79bf231 @@ -0,0 +1,41 @@ +/*************************************************************************** + * Copyright (C) 2007 by Thomas Keller * + * address@hidden * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef ADDEDITATTRIBUTE_H +#define ADDEDITATTRIBUTE_H + +#include "ui_add_edit_attribute.h" +#include "Dialog.h" +#include "vocab.h" + +class AddEditAttribute : public Dialog, private Ui::AddEditAttributeDialog +{ + Q_OBJECT + +public: + AddEditAttribute(QWidget *, + const QString & key = QString(), const QString & value = QString()); + ~AddEditAttribute(); + QString getKey() const { return attrKey->currentText(); } + QString getValue() const { return attrValue->text(); } +}; + +#endif + ============================================================ --- src/view/dialogs/FileHistory.cpp 86e856616418ed7875cbd92515116b406947abc4 +++ src/view/dialogs/FileHistory.cpp 86e856616418ed7875cbd92515116b406947abc4 @@ -0,0 +1,139 @@ +/*************************************************************************** + * Copyright (C) 2007 by Thomas Keller * + * address@hidden * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "FileHistory.h" +#include "FileDiff.h" + +FileHistory::FileHistory(QWidget * parent, const QString & fileName) + : Dialog(parent) +{ + setupUi(this); + Dialog::init(); + + // OSX sheet-alike dialog + setWindowFlags(Qt::Sheet); + + splitter->init(); + + changeModel = new GetContentChanged(this); + certsModel = new Certs(this); + + // assign the models to the views + revisionFileList->setModel(changeModel); + certList->setModel(certsModel); + + // display the certs of a selected revision on click + connect( + revisionFileList, SIGNAL(clicked(const QModelIndex &)), + this, SLOT(readCerts(const QModelIndex &)) + ); + + connect( + selectFirst, SIGNAL(clicked()), + this, SLOT(setFirstRevision()) + ); + + connect( + selectSecond, SIGNAL(clicked()), + this, SLOT(setSecondRevision()) + ); + + connect( + showDiff, SIGNAL(clicked()), + this, SLOT(showDiffDialog()) + ); + + connect( + revisionFileList, SIGNAL(clicked(const QModelIndex &)), + this, SLOT(revisionFileListClicked(const QModelIndex &)) + ); + + QString title = windowTitle(); + setWindowTitle(title.arg(fileName)); + + // read the changes + I(changeModel->readChanges(fileName)); +} + +FileHistory::~FileHistory() +{ + delete changeModel; + delete certsModel; +} + +void FileHistory::readCerts(const QModelIndex & index) +{ + if (!index.isValid()) return; + + QModelIndex revIdx = changeModel->index(index.row(), 0, QModelIndex()); + QString rev(revIdx.data().toString()); + + if (!certsModel->readCerts(rev)) + { + C(QString("Couldn't read certs for %1").arg(rev)); + } +} + +void FileHistory::setFirstRevision() +{ + QModelIndex revIdx = changeModel->index(curRow, 0, QModelIndex()); + QModelIndex fileIdx = changeModel->index(curRow, 1, QModelIndex()); + I(revIdx.isValid() && fileIdx.isValid()); + + firstRevision = revIdx.data().toString(); + fileName = fileIdx.data().toString(); + + if (secondRevision.size() > 0) showDiff->setEnabled(true); + + selectFirst->setText(tr("First: %1...").arg(firstRevision.left(12))); +} + +void FileHistory::setSecondRevision() +{ + QModelIndex revIdx = changeModel->index(curRow, 0, QModelIndex()); + I(revIdx.isValid()); + + secondRevision = revIdx.data().toString(); + + if (firstRevision.size() > 0) showDiff->setEnabled(true); + + selectSecond->setText(tr("Second: %1...").arg(secondRevision.left(12))); +} + +void FileHistory::revisionFileListClicked(const QModelIndex & index) +{ + if (!index.isValid()) return; + + curRow = index.row(); + + // make sure both button are enabled + selectFirst->setEnabled(true); + selectSecond->setEnabled(true); +} + +void FileHistory::showDiffDialog() +{ + I(firstRevision.size() > 0 && secondRevision.size() > 0); + + FileDiff dlg(this); + dlg.init(fileName, firstRevision, secondRevision); + dlg.exec(); +} + ============================================================ --- src/view/dialogs/FileHistory.h ed76ac56e2b114a67196d38bae14c01b8b9fda63 +++ src/view/dialogs/FileHistory.h ed76ac56e2b114a67196d38bae14c01b8b9fda63 @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (C) 2007 by Thomas Keller * + * address@hidden * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef FILE_HISTORY_H +#define FILE_HISTORY_H + +#include "Dialog.h" +#include "ui_file_history.h" +#include "Certs.h" +#include "GetContentChanged.h" + +class FileHistory : public Dialog, private Ui::FileHistoryDialog +{ + Q_OBJECT + +public: + FileHistory(QWidget *, const QString &); + ~FileHistory(); + +private: + GetContentChanged * changeModel; + Certs * certsModel; + + int curRow; + QString firstRevision; + QString secondRevision; + QString fileName; + +private slots: + void readCerts(const QModelIndex &); + void setFirstRevision(); + void setSecondRevision(); + void revisionFileListClicked(const QModelIndex &); + void showDiffDialog(); +}; + +#endif ============================================================ --- src/view/dialogs/UnaccountedRenames.cpp 938d5eac2b9884b18445e4eacde26c5d31dea0e8 +++ src/view/dialogs/UnaccountedRenames.cpp 938d5eac2b9884b18445e4eacde26c5d31dea0e8 @@ -0,0 +1,104 @@ +/*************************************************************************** + * Copyright (C) 2007 by Thomas Keller * + * address@hidden * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "UnaccountedRenames.h" +#include "IconProvider.h" + +#include +#include + +UnaccountedRenames::UnaccountedRenames(QWidget * parent, const QMap & r): + Dialog(parent), renames(r) +{ + setupUi(this); + Dialog::init(); + + // OSX sheet-alike dialog + setWindowFlags(Qt::Sheet); + + connect( + renameTree, SIGNAL(itemClicked(QTreeWidgetItem *, int)), + this, SLOT(itemClicked(QTreeWidgetItem *, int)) + ); + + renameTree->setHeaderLabels(QStringList() + << tr("Missing paths and possible rename targets") + << tr("File ID (if applicable)") + ); + + renameTree->header()->resizeSection(0, 300); + + QFont boldFont; + boldFont.setBold(true); + IconProvider * iconProvider = IconProvider::singleton(); + + QMapIterator i(renames); + while (i.hasNext()) + { + i.next(); + FileEntry sen = i.key(); + FileEntryList list = i.value(); + QTreeWidgetItem * source = new QTreeWidgetItem(renameTree); + source->setFlags(Qt::ItemIsEnabled); + source->setText(0, QString("%1 (%2)").arg(sen.path).arg(list.size())); + source->setData(0, Qt::UserRole, QVariant(sen.path)); + source->setFont(0, boldFont); + source->setIcon(0, sen.is_dir ? + iconProvider->getPlainFolderIcon() : + iconProvider->getPlainFileIcon()); + source->setText(1, sen.fileid.isEmpty() ? tr("n/a") : sen.fileid); + source->setFont(1, boldFont); + + foreach (FileEntry en, list) + { + QTreeWidgetItem * target = new QTreeWidgetItem(source); + target->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); + target->setCheckState(0, Qt::Unchecked); + target->setText(0, en.path); + target->setToolTip(0, en.fileid.isEmpty() || sen.fileid != en.fileid ? + tr("matched by name") : + tr("matched by content")); + + target->setText(1, en.fileid.isEmpty() ? tr("n/a") : en.fileid); + } + } +} + +UnaccountedRenames::~UnaccountedRenames() +{ +} + +void UnaccountedRenames::itemClicked(QTreeWidgetItem * item, int column) +{ + if (item == 0 || column == -1) return; + QTreeWidgetItem * parent = item->parent(); + // top level item clicked + if (!parent) return; + + Qt::CheckState state = item->checkState(0); + + for (int i=0, j=parent->childCount(); ichild(i)->setCheckState(0, Qt::Unchecked); + } + + item->setCheckState(0, state); +} + ============================================================ --- src/view/dialogs/UnaccountedRenames.h 0691bf54c8ff2d8606e88eb855613a4977ad8b9d +++ src/view/dialogs/UnaccountedRenames.h 0691bf54c8ff2d8606e88eb855613a4977ad8b9d @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (C) 2007 by Thomas Keller * + * address@hidden * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef UNACCOUNTED_RENAMES_H +#define UNACCOUNTED_RENAMES_H + +#include "Dialog.h" +#include "vocab.h" +#include "ui_unaccounted_renames.h" + +class UnaccountedRenames : public Dialog, private Ui::UnaccountedRenamesDialog +{ + Q_OBJECT + +public: + UnaccountedRenames(QWidget *, const QMap &); + ~UnaccountedRenames(); + +private: + QMap renames; + +private slots: + void itemClicked(QTreeWidgetItem *, int); +}; + +#endif ============================================================ --- tests/MonotoneTest.h 73986287a1873b2e72dbc62743a7cdd9ab317fb7 +++ tests/MonotoneTest.h 73986287a1873b2e72dbc62743a7cdd9ab317fb7 @@ -0,0 +1,69 @@ + +#include +#include +#include +#include "Monotone.h" + +class MonotoneTest: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + QVERIFY(Monotone::checkProgramVersion("mtn")); + QDir tmpDir = QDir::temp(); + QVERIFY(tmpDir.mkdir("guitone-test")); + QVERIFY(tmpDir.cd("guitone-test")); + + QString out; + QVERIFY(runmtn(QStringList() << "init", out)); + QVERIFY(runmtn(QStringList() << "setup" << "-b foo" << ".", out)); + + mtn = new Monotone(0); + QVERIFY(mtn->loadWorkspace(tmpDir.absolutePath())); + } + + void getOptionTest() + { + QVERIFY(true); + } + + void cleanupTestCase() + { + QDir tmpDir = QDir::temp(); + removePath(tmpDir.filePath("guitone-test")); + delete mtn; + } + +private: + bool runmtn(const QStringList & a, QString & output) + { + QDir tmpDir = QDir::temp(); + Q_ASSERT(tmpDir.cd("guitone-test")); + QStringList args; + args << QString("-d %1").arg(tmpDir.filePath("test.mtn")) << a; + return Monotone::runCommand("mtn", args, output); + } + + void removePath(const QString & path) + { + QFileInfo fileInfo(path); + if (fileInfo.isDir()) + { + QDir dir(path); + QStringList entries = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot); + foreach (QString entry, entries) + { + removePath(dir.filePath(entry)); + } + dir.rmdir(path); + return; + } + QFile::remove(path); + return; + } + + Monotone * mtn; +}; + ============================================================ --- tests/StdioParserTest.h 3974e240f162fc6cebbd6df97c3f36ed0440817d +++ tests/StdioParserTest.h 3974e240f162fc6cebbd6df97c3f36ed0440817d @@ -0,0 +1,58 @@ + +#include +#include +#include "StdioParser.h" + +/* + StdioParser(const QByteArray &); + + bool parse(); + inline int getCommandNumber() const { return commandNumber; } + inline int getErrorCode() const { return errorCode; } + inline char getChunkType() const { return chunkType; } + inline int getChunkSize() const { return chunkSize; } + inline QByteArray getPayload() const { return payload; } + +*/ + +class StdioParserTest: public QObject +{ + Q_OBJECT + +private slots: + void emptyPayloadTest() + { + QByteArray in("2:0:l:0:"); + StdioParser parser(in); + QVERIFY(parser.parse()); + QVERIFY(parser.getCommandNumber() == 2); + QVERIFY(parser.getErrorCode() == 0); + QVERIFY(parser.getChunkType() == 'l'); + QVERIFY(parser.getChunkSize() == 0); + QByteArray out = parser.getPayload(); + QVERIFY(out.isNull()); + } + + void missingPayloadTest() + { + QByteArray in("0:0:m:20:ten bytes!"); + StdioParser parser(in); + QVERIFY(!parser.parse()); + } + + void morePayloadTest() + { + QByteArray in("0:0:m:10:ten bytes!BUT HERE IS MORE"); + StdioParser parser(in); + QVERIFY(parser.parse()); + QVERIFY(parser.getCommandNumber() == 0); + QVERIFY(parser.getErrorCode() == 0); + QVERIFY(parser.getChunkType() == 'm'); + QVERIFY(parser.getChunkSize() == 10); + QByteArray out = parser.getPayload(); + QVERIFY(out == "ten bytes!"); + QByteArray left = parser.getLeftBytes(); + QVERIFY(left == "BUT HERE IS MORE"); + } +}; + ============================================================ --- NEWS c5dfe130fb53d6553fc530e7e94140ffd99fe2c3 +++ NEWS 949d2a577317be4215e33daa7157824f994577d8 @@ -1,3 +1,13 @@ +????-??-?? + - new: possibility to display the history of a single file in chronological + order (right-click on any tracked file and click "History") + - new: find unaccounted renames in your workspace (i.e. finds those renames + which only happened in the filesystem). Matches files by content and + directories by name (actual rename is not yet implemented due to a missing + version of mtn automate rename) + - new: add, drop and edit file attributes + - bugfix: the workspace is now properly updated after a commit + 2007-08-20 (0.6.4) - compatibility with monotone 0.36 (and any upcoming monotone with an interface_version above 5.0 and below 5.99) ============================================================ --- guitone.pro 33545cda1a516aede1c2b494f1432818727048c9 +++ guitone.pro 793a62ed84e3c859c3963721260c4e6fa379f1fa @@ -35,6 +35,9 @@ HEADERS = src/view/MainWindow.h \ src/view/dialogs/ChangesetBrowser.h \ src/view/dialogs/RevisionManifest.h \ src/view/dialogs/CommitRevision.h \ + src/view/dialogs/FileHistory.h \ + src/view/dialogs/UnaccountedRenames.h \ + src/view/dialogs/AddEditAttribute.h \ src/monotone/Monotone.h \ src/monotone/MonotoneDelegate.h \ src/monotone/FileExporter.h \ @@ -43,7 +46,7 @@ HEADERS = src/view/MainWindow.h \ src/model/Inventory.h \ src/model/InventoryItem.h \ src/model/InventoryProxyModel.h \ - src/model/Attributes.h \ + src/model/GetAttributes.h \ src/model/Select.h \ src/model/Certs.h \ src/model/ContentDiff.h \ @@ -59,9 +62,11 @@ HEADERS = src/view/MainWindow.h \ src/model/Ancestors.h \ src/model/GetBranchLog.h \ src/model/GetRevision.h \ + src/model/GetContentChanged.h \ src/util/IconProvider.h \ src/util/AbstractParser.h \ src/util/BasicIOParser.h \ + src/util/BasicIOWriter.h \ src/util/Settings.h \ src/util/DiffParser.h \ src/util/SignalWaiter.h \ @@ -92,6 +97,9 @@ SOURCES += src/view/MainWindow.cpp \ src/view/dialogs/ChangesetBrowser.cpp \ src/view/dialogs/RevisionManifest.cpp \ src/view/dialogs/CommitRevision.cpp \ + src/view/dialogs/FileHistory.cpp \ + src/view/dialogs/UnaccountedRenames.cpp \ + src/view/dialogs/AddEditAttribute.cpp \ src/monotone/Monotone.cpp \ src/monotone/MonotoneDelegate.cpp \ src/monotone/FileExporter.cpp \ @@ -100,7 +108,7 @@ SOURCES += src/view/MainWindow.cpp \ src/model/Inventory.cpp \ src/model/InventoryItem.cpp \ src/model/InventoryProxyModel.cpp \ - src/model/Attributes.cpp \ + src/model/GetAttributes.cpp \ src/model/Select.cpp \ src/model/Certs.cpp \ src/model/ContentDiff.cpp \ @@ -116,9 +124,11 @@ SOURCES += src/view/MainWindow.cpp \ src/model/Ancestors.cpp \ src/model/GetBranchLog.cpp \ src/model/GetRevision.cpp \ + src/model/GetContentChanged.cpp \ src/util/IconProvider.cpp \ src/util/AbstractParser.cpp \ src/util/BasicIOParser.cpp \ + src/util/BasicIOWriter.cpp \ src/util/Settings.cpp \ src/util/DiffParser.cpp \ src/util/SignalWaiter.cpp \ @@ -142,7 +152,11 @@ FORMS += res/forms/select_revision.ui res/forms/changeset_browser.ui \ res/forms/manifest.ui \ res/forms/commit_revision.ui \ - res/forms/application_update.ui + res/forms/application_update.ui \ + res/forms/file_history.ui \ + res/forms/unaccounted_renames.ui \ + res/forms/add_edit_attribute.ui + UI_DIR = tmp OBJECTS_DIR = tmp @@ -184,9 +198,11 @@ macx { # macx { - # add sources for Sparkle - HEADERS += src/util/CocoaUtil.h - SOURCES += src/util/CocoaUtil.mm + # add specific Mac sources + HEADERS += src/util/CocoaUtil.h \ + src/view/MacStartMenu.h + SOURCES += src/util/CocoaUtil.mm \ + src/view/MacStartMenu.cpp # add the Sparkle and the Carbon framework QMAKE_LFLAGS += -framework Sparkle -framework Carbon ============================================================ --- notes/IDEAS 0f7d32bac1e3d7405a733c7548508969064efe21 +++ notes/IDEAS bdf47a7c58a2a0c0f183ea9ef59290a46d7dbc3c @@ -1,6 +1,7 @@ mid- to longterm, for a list of definite This file lists some ideas what could be improved / implemented in guitone mid- to longterm, for a list of definite changes see TODO: +* markdown-alike functionality for diff view * make changed items view configurable (currently they're just bold) * integrate support to make guitone a kpart module under KDE * add functionality which is currently missing from monotone (update, ...) ============================================================ --- notes/RELEASE_CHECKLIST 3dd539c8e9b77fb957571a2c4dcb641ce25a44f0 +++ notes/RELEASE_CHECKLIST 78816f3f795e8651b5ac5fd7b3870b735c7d3bd2 @@ -24,7 +24,7 @@ 5) ZIP release (binary, win32, mingw): $ mv bin/guitone.exe bin/guitone-MAJOR.MINOR.BUGFIX $ cp COPYING NEWS README bin/guitone-MAJOR.MINOR.BUGFIX $ cp $QTDIR/bin/mingwm10.dll $QTDIR/bin/QtCore4.dll $QTDIR/bin/QtGui4.dll \ - $QTDIR/bin/QtXml4.dll $QTDIR/bin/QtNetwork4.dll \ + $QTDIR/bin/QtXml4.dll $QTDIR/bin/QtNetwork4.dll $QTDIR/bin/QtSvg4.dll \ bin/guitone-MAJOR.MINOR.BUGFIX Finally _test_ the binary and zip the folder. ============================================================ --- notes/TODO ea5eaa751fa11645c142bca08911a145ef87d4a5 +++ notes/TODO 336f24bc9bc9f3914981275846cbd7b05ff7e19e @@ -1,2 +1,11 @@ Current TODO, for "long-term" stuff see Current TODO, for "long-term" stuff see IDEAS. +* more testing with commit functionality (seems to make problems + especially if binary files are added / edited) +* better version detection from rss +* fix segfault on OSX when doing Cmd+Q (seems as not everything + gets cleaned afterwards nicely) +* fix non-localization for MacStartMenu +* fix crash in ChangesetBrowser on all platforms when clicking on + a branch name (Monotone-wrapper related) + ============================================================ --- res/forms/application_update.ui cfb6889da734fdfa20f68943c73d93983951ce5a +++ res/forms/application_update.ui 7d2b3993e719ba0fea62c815d0cee25d6fd48562 @@ -42,6 +42,13 @@ 6 + + + Visit website + + + + Qt::Horizontal @@ -55,17 +62,13 @@ - - - Visit website - - - - Close + + true + ============================================================ --- res/forms/changeset_browser.ui b305fba833874efa7b6b53773d8cea303c0366d0 +++ res/forms/changeset_browser.ui ac59737143d7aac046048608d14ed2dbdefd23b6 @@ -122,6 +122,9 @@ close + + true + @@ -134,7 +137,7 @@ Splitter QSplitter -
../Splitter.h
+
Splitter.h
TreeView ============================================================ --- res/forms/commit_revision.ui 7743280f847be7c2fd842e55b24e48be78d2b6ab +++ res/forms/commit_revision.ui 869cc6762e7b033d8186dc527e9d96ab5e77ff49 @@ -249,41 +249,14 @@ - - - 0 + + + Qt::Horizontal - - 6 + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Abort - - - - - - - Commit - - - - + @@ -305,12 +278,12 @@ setEnabled(bool) - 89 - 458 + 100 + 514 - 238 - 461 + 456 + 515 @@ -321,44 +294,44 @@ setEnabled(bool) - 87 - 425 + 98 + 484 - 243 - 427 + 456 + 485 - abort - clicked() + buttonBox + rejected() CommitRevision reject() - 294 - 502 + 302 + 541 - 146 - 487 + 252 + 561 - commitRev - clicked() + buttonBox + accepted() CommitRevision accept() - 371 - 492 + 411 + 543 - 45 - 483 + 392 + 560 ============================================================ --- res/forms/file_diff.ui 7892257391ce4faa476cd26cb04e424ac91a4e2d +++ res/forms/file_diff.ui 9999a04c668f9a40b1a9b20f7ecd0812e8dc7cd5 @@ -5,8 +5,8 @@ 0 0 - 553 - 436 + 676 + 491 @@ -47,6 +47,68 @@ + + + Show + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + first (%1) + + + + + + + second (%2) + + + + + + + both + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 @@ -55,47 +117,6 @@ 6 - - - Show Version - - - - - - - 0 - - - 6 - - - - - Left - - - - - - - Right - - - - - - - Both - - - true - - - - - - Qt::Horizontal @@ -109,10 +130,27 @@ + + + Prev + + + + + + + Next + + + + Close + + true + @@ -123,15 +161,15 @@ + DiffStatusView + QWidget +
../DiffStatusView.h
+
+ DiffView QTreeView
../DiffView.h
- - DiffStatusView - QWidget -
../DiffStatusView.h
-
============================================================ --- res/forms/key_management.ui edec01c656964ce19977a8305c380207fa222bff +++ res/forms/key_management.ui 4913ed46e0685544cb5e3cf7cdf98b54aacd42ce @@ -79,6 +79,9 @@ Close + + true +
============================================================ --- res/forms/main_window.ui 6c5b7e9f7df0fc48722a97cf9e48cbc51ea46e2d +++ res/forms/main_window.ui 2a502c4c0a34fefa6c210f0f74f6e98a7ad6fe2b @@ -15,27 +15,33 @@ :/icons/guitone.png + + false + - - 9 - - - 6 - - 1 + 0 - + 0 - + 0 + + 0 + + + 0 + + + 0 + @@ -61,7 +67,11 @@ QAbstractItemView::ExtendedSelection - + + + true + + @@ -69,12 +79,21 @@ - + 0 - + 0 + + 0 + + + 0 + + + 0 + @@ -143,21 +162,6 @@ - - - Database - - - - - - - - Workspace - - - - File @@ -187,6 +191,23 @@ + + + Database + + + + + + + + Workspace + + + + + + @@ -450,14 +471,22 @@ Check for updates + + + Reload workspace + + + Ctrl+R + + + + + Find unaccounted renames + + - Splitter - QSplitter -
../Splitter.h
-
- InventoryView QTreeView
../InventoryView.h
@@ -467,6 +496,12 @@ QTreeView
../AttributesView.h
+ + Splitter + QSplitter +
../Splitter.h
+ 1 +
============================================================ --- res/forms/manifest.ui b0411bd9c26399c10dd09a486cc6403b80af3f7c +++ res/forms/manifest.ui 5b3e49e7bec6ba679c67b10ef1c9c0ee7e6799d2 @@ -77,6 +77,9 @@ Close + + true + ============================================================ --- res/forms/preferences.ui 4c7a832f0d210371eb68882a1f736ff150affdff +++ res/forms/preferences.ui 9898dad2631df0a8a5cb6f06fc5055160b7e24d2 @@ -214,17 +214,20 @@ - + - OK + Cancel - + - Cancel + OK + + true + ============================================================ --- res/forms/revision_diff.ui d4d25a844c0a4a141bc9ac01933d96371b7116c0 +++ res/forms/revision_diff.ui 56845d2399bd6d4a4e6936b65311cb63a599ba99 @@ -56,6 +56,9 @@ Close + + true + ============================================================ --- res/forms/select_revision.ui c1cecd2c790c38b27a87df2bea6e99ca65579c49 +++ res/forms/select_revision.ui b901de21917e7ec4fa9b7ebfa3f1bb5a93bdcf47 @@ -148,6 +148,9 @@ QAbstractItemView::NoSelection + + QAbstractItemView::ScrollPerPixel + 10 @@ -182,17 +185,20 @@ - + - OK + Cancel - + - Cancel + OK + + true + @@ -203,15 +209,15 @@ + Splitter + QSplitter +
Splitter.h
+
+ TreeView QTreeView
TreeView.h
- - Splitter - QSplitter -
Splitter.h
-
selectorBox ============================================================ --- res/guitone-icon.svg f485e11fe3d6d8003fec375967c5375a4ecb9b2f +++ res/guitone-icon.svg 00effff3ab338037f85f83a29ee1a41e1f6525b6 @@ -9,13 +9,13 @@ xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="214.62422" - height="260.50723" + width="260" + height="260" id="svg1944" sodipodi:version="0.32" inkscape:version="0.44.1" version="1.0" - sodipodi:docbase="/home/tkeller/private/guitone/guitone/res" + sodipodi:docbase="/home/tkeller/private/guitone/res" sodipodi:docname="guitone-icon.svg"> @@ -214,15 +214,17 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="0.35" + inkscape:zoom="0.7" inkscape:cx="350" - inkscape:cy="520" + inkscape:cy="62.857143" inkscape:document-units="px" inkscape:current-layer="layer1" inkscape:window-width="937" inkscape:window-height="582" - inkscape:window-x="550" - inkscape:window-y="311" /> + inkscape:window-x="165" + inkscape:window-y="152" + height="260px" + width="260px" /> @@ -241,7 +243,7 @@ transform="translate(-138.4022,-199.2514)"> + transform="translate(160.4022,199.2514)"> guitone-logo.svg i18n/guitone_de.qm icons/guitone.png + icons/red_dot.png + icons/yellow_dot.png + icons/green_dot.png + icons/blue_dot.png + icons/arrow_down.png overlays/added.png overlays/added_missing.png overlays/cdup.png ============================================================ --- res/i18n/guitone_de.ts 7d2efd4ce19b2d6b7fe344dfebb06da025cc22c1 +++ res/i18n/guitone_de.ts 368821342cae5393b64812ebc3addd7032346589 @@ -1,14 +1,9 @@ About - - <small>Version %1.%2</small> - <small>Version %1.%2</small> - - <br/><br/>Authors: <a href="mailto:address@hidden">Thomas Keller</a>, <a href="mailto:address@hidden">Ingo Maindorfer</a> and <a href="mailto:address@hidden">Jean-Louis Fuchs</a>.<br/><br/>This program is free software; you can redistribute it and/or modify<br/>it under the terms of the GNU General Public License as published by<br/>the Free Software Foundation; either version 2 of the License, or<br/>(at your option) any later version.<br/><br/>Bugs? Suggestions? Help? <a href="http://guitone.thomaskeller.biz">guitone Homepage</a><br/><br/>Many thanks go to the friendly guys at <a href="irc://irc.freenode.net/qt">#qt</a> for their helpful<br/>comments during endless coding sessions - you guys rock! <br/><br/>Autoren: <a href="mailto:address@hidden">Thomas Keller</a>, <a href="mailto:address@hidden">Ingo Maindorfer</a> und <a href="mailto:address@hidden">Jean-Louis Fuchs</a><br/><br/>Dieses Programm ist freie Software. Sie können es unter den<br/>Bedingungen der GNU General Public License, wie von der<br/>Free Software Foundation veröffentlicht, weitergeben und/oder<br/>modifizieren, entweder gemäß Version 2 der Lizenz oder <br/>(nach Ihrer Option) jeder späteren Version.<br/><br/>Fehler? Vorschläge? Hilfe? <a href="http://guitone.thomaskeller.biz">guitone Webseite</a><br/><br/>Vielen Dank an die freundlichen Seelen auf <a href="irc://irc.freenode.net/qt">#qt</a> für die Hilfe während der<br/>unendlich langen Programmierstunden - Ihr Jungs s eid Spitze! @@ -33,6 +28,24 @@ + AddEditAttributeDialog + + + Add / edit an attribute + Hinzufügen / Editieren eines Attributes + + + + Key + Schlüssel + + + + Value + Wert + + + Ancestors @@ -48,7 +61,7 @@ Eine neue Version von guitone ist verfügbar! - + Visit website Webseite besuchen @@ -59,42 +72,22 @@ - Attributes + AttributesView - - added - hinzugefügt + + add new attribute + Neues Attribut hinzufügen - - dropped - entfernt + + edit attribute + Attribut editieren - - changed - verändert + + drop attribute + Attribut löschen - - - unchanged - unverändert - - - - Key - Schlüssel - - - - Value - Wert - - - - State - Status - Branches @@ -374,16 +367,6 @@ Zeige Änderungen gegenüber Elternrevision - - Select all - Alle auswählen - - - - Invert Selection - Auswahl umkehren - - Changelog entry Changelog-Eintrag @@ -406,20 +389,20 @@ Abort - Abbrechen + Abbrechen Commit - Einpflegen + Einpflegen - + No changes Keine Änderungen - + The current workspace has no committable changes. Der derzeitige Arbeitsbereich hat keine Änderungen zum Einpflegen. @@ -473,7 +456,7 @@ den Schlüssel, mit dem Sie diese Revisi Revision committed - Revision eingepflegt + Revision eingepflegt @@ -483,7 +466,7 @@ In any case close the current view to av %1 In any case close the current view to avoid committing the same revision again. - Die Revision wurde erfolgreich eingepflegt. Da jedoch momentan noch kein "update"-Kommando über die monotone-Schnittstelle verfügbar ist, konnte Ihr Arbeitsbereich nicht automatisch aktualisiert werden. + Die Revision wurde erfolgreich eingepflegt. Da jedoch momentan noch kein "update"-Kommando über die monotone-Schnittstelle verfügbar ist, konnte Ihr Arbeitsbereich nicht automatisch aktualisiert werden. Sie müssen dies entweder per Hand erledigen oder die neue Revision auschecken: %1 @@ -495,57 +478,67 @@ In jedem Fall sollte der derzeitige Arbe Please enter the name of an alternative branch. Bitte geben Sie den Namen eines alternativen Zweigs an. + + + Could not commit revision + Konnte Revision nicht einpflegen + + + + Unable to commit the revision - this may be a bug in guitone or a bug in monotone itself. Please take a closer look at thelogs and optionally send in a bug report. + Revision konnte nicht eingepflegt werden. Dies ist entweder ein Fehler in guitone oder monotone selbst. Bitte beachten Sie . + + + + Unable to commit the revision - this may be a bug in guitone or a bug in monotone itself. Please take a closer look at the logs and optionally send in a bug report. + Revision konnte nicht eingepflegt werden. Dies ist entweder ein Fehler in guitone oder monotone selbst. Bitte beachten Sie die Ausgaben im Log und senden Sie optional eine Fehlermeldung ein. + ContentDiff - + %1 (binary) %1 (binär) - + %1 (%2 hunks) %1 (%2 Bereiche) - + Line Zeile - + File/Content Datei/Inhalt - DatabaseView + FileDiff - - Database - Datenbank + + workspace parent + Vorgänger des Arbeitsbereichs - - All Changesets - Alle Änderungen + + workspace revision + Arbeitsbereichrevision - - 50 More Changesets - 50 weitere Änderungen + + File has not changed + Datei wurde nicht geändert - - Done - Beenden + + The file has not been changed between these revisions. + Die Datei wurde zwischen diesen Revisionen nicht geändert. - - - ... - ... - FileDiffDialog @@ -555,39 +548,44 @@ In jedem Fall sollte der derzeitige Arbe Unterschiede in der Datei "%1" - - Show Version - Zeige Version + + Close + Schließen - - Left - links + + Show + Zeige - - Right - rechts + + first (%1) + erste (%1) - - Both + + second (%2) + zweite (%2) + + + + both beide - - Close - Schließen + + Prev + Vorherige + + + Next + Nächste + FileExporter - - Exporting files... - Exportiere Dateien... - - Abort Abbrechen @@ -599,6 +597,47 @@ In jedem Fall sollte der derzeitige Arbe + FileHistory + + + First: %1... + Erste: %1... + + + + Second: %1... + Zweite: %1... + + + + FileHistoryDialog + + + History of %1 + Geschichte von %1 + + + + Select as first revision + Als erste Revision auswählen + + + + Select as second revision + Als zweite Revision auswählen + + + + Show differences + Unterschiede anzeigen + + + + Close + Schließen + + + GenerateKeypair @@ -664,14 +703,65 @@ In jedem Fall sollte der derzeitige Arbe + GetAttributes + + + empty + leer + + + + added + hinzugefügt + + + + dropped + entfernt + + + + changed + verändert + + + + unchanged + unverändert + + + + Key + Schlüssel + + + + Value + Wert + + + + GetContentChanged + + + Revision ID + Revisions-ID + + + + Path in this revision + Pfad in dieser Revision + + + GetFile - + Line Zeile - + Content Inhalt @@ -692,12 +782,12 @@ In jedem Fall sollte der derzeitige Arbe Guitone - + Error Fehler - + The path to the monotone binary is either invalid or points to an older version of monotone. Guitone requires monotone version %1 or a monotone with interface version %2 or later. Der Pfad zur ausführbaren Datei von monotone ist entweder ungültig oder zeigt auf eine ältere Version von monotone. Guitone benötigt monotone Version %1 oder ein monotone mit einer Interface-Version %2 oder neuer. @@ -707,7 +797,7 @@ In jedem Fall sollte der derzeitige Arbe Kritischer monotone-Fehler - + Select your workspace... Wählen Sie Ihren Arbeitsbereich aus... @@ -801,275 +891,200 @@ In jedem Fall sollte der derzeitige Arbe InventoryView - - &Add - &Hinzufügen - - - + Add to workspace Zum Arbeitsbereich hinzufügen - - &Remove - En&tfernen - - - + Remove from workspace Vom Arbeitsbereich entfernen - - &Commit - &Einpflegen - - - + Commit Einpflegen - - I&gnore - Datei &ignorieren - - - + Ignore file Datei ignorieren - - &Unignore - Datei nicht ign&orieren - - - + Unignore file Datei nicht mehr ignorieren - - R&evert - &Zurücksetzen - - - + Revert uncommitted changes Nicht eingepflegte Änderungen verwerfen - - Rena&me - Um&benennen - - - + Rename file Datei umbenennen - - D&iff - U&nterschiede anzeigen - - - + Diff against base revision Unterschiede im Vergleich zur Basisrevision anzeigen - - &Go into - &Wechseln zu - - - + Go into the directory Wechsle in das Verzeichnis - - &Open - &Öffnen - - - + Open in default program In Standardprogramm öffnen - + Error Fehler - + The file you're trying to open does not exist. Die Datei, die Sie versucht haben zu öffnen, existiert nicht. - + Unable to open files on your platform - please contact the author about this problem. Kann keine Dateien auf Ihrer Plattform öffnen - bitte kontaktieren Sie den Autor über dieses Problem. - - D&iff all - Alle U&nterschiede anzeigen - - - + Show all differences Zeigt Unterschiede in allen Dateien - + No common action for selected items Keine gemeinsamen Aktionen auf selektierte Einträge anwendbar - + Go into Wechseln zu - + Open Öffnen - + Add Hinzufügen - + Remove Entfernen - + Ignore Datei ignorieren - + Unignore Datei nicht ignorieren - + Revert Zurücksetzen - + Diff Unterschiede anzeigen - + Diff all Alle Unterschiede anzeigen - + Rename Umbenennen - + Add %1 items Füge %1 Einträge hinzu - - Add multiple files - Fügt mehrere Einträge hinzu - - - + Remove %1 items Entferne %1 Einträge - - Remove multiple files - Entfernt mehrere Einträge - - - + Commit %1 items Pflege %1 Einträge ein - - Commit multiple files - Pflegt mehrere Einträge ein - - - + Ignore %1 items Ignoriere %1 Einträge - - Ignore multiple files - Ignoriert mehrere Einträge - - - + Unignore %1 items Ignoriere %1 Einträge nicht mehr - - Unignore multiple files - Ignoriere mehrere Einträge nicht mehr - - - + Revert %1 items Setze %1 Einträge zurück - - Revert multiple files - Setze mehrere Einträge zurück - - - + Add multiple items Füge mehrere Einträge hinzu - + Remove multiple items Entferne mehrere Einträge - + Commit multiple items Pflege mehrere Einträge ein - + Ignore multiple items Ignoriere mehrere Einträge - + Unignore multiple items Ignoriere mehrere Einträge nicht mehr - + Revert multiple items Setze mehrere Einträge zurück + + + History + Geschichte + + + + Display the history of this file + Zeige die Geschichte dieser Datei an + KeyManagement @@ -1153,209 +1168,182 @@ In jedem Fall sollte der derzeitige Arbe + MacStartMenu + + + No updates available + Keine Aktualisierungen verfügbar + + + + Your version of guitone (%1) is already up-to-date. + Ihre Version von guitone (%1) ist bereits aktuell. + + + + Select your workspace... + Wählen Sie Ihren Arbeitsbereich aus... + + + + Select your database... + Wählen Sie eine Datenbank aus... + + + + monotone Databases (*.mtn *.db) + monotone-Datenbanken (*.mtn *.db) + + + MainWindow - + View Ansicht - + Help Hilfe - + Workspace Arbeitsbereich - + File Datei - + Recent Workspaces Vorherige Arbeitsbereiche - + Open Workspace Arbeitsbereich öffnen - + Ctrl+O Strg+O - + No previous workspaces available. Keine vorherigen Arbeitsbereiche verfügbar. - - Preferences.... - Einstellungen... - - - + Ctrl+P Strg+P - + Quit Beenden - + Ctrl+Q Strg+Q - + Hide ignored files Ignorierte Dateien verstecken - + Ctrl+H Strg+H - + All files Alle Dateien - - A - A - - - + All changed files Alle geänderten Dateien - - C - G - - - + Patched files Inhaltlich geänderte Dateien - - P - P - - - + Added files Hinzugefügte Dateien - - N - H - - - + Removed files Entfernte Dateien - - D - E - - - + Renamed files Umbenannte Dateien - - R - U - - - + Missing files Fehlende Dateien - - M - F - - - + Unknown files Unbekannte Dateien - - U - K - - - + Ignored files Ignorierte Dateien - - I - I - - - + Expand tree Baum aufklappen - + Ctrl+T Strg+T - - Switch revision - Revision wechseln - - - + Ctrl+R - Strg+R + Strg+R - + Key management Schlüsselverwaltung - + Ctrl+K Strg+K - + About Qt Über Qt - + About guitone Über guitone - + Show Zeige @@ -1370,208 +1358,172 @@ In jedem Fall sollte der derzeitige Arbe Wählen Sie Ihren Arbeitsbereich aus... - + Loading aborted Laden abgebrochen - - Invalid workspace - Ungültiger Arbeitsbereich - - - - The chosen directory is no monotone workspace! - Das gewählte Verzeichnis ist kein monotone-Arbeitsverzeichnis! - - - - Unable to execute command - Konnte Kommando nicht ausführen - - - - Unable to execute '%1' - maybe another command is still running? - Konnte '%1' nicht ausführen - eventuell läuft noch ein anderes Kommando? - - - + Show ignored files Zeige ignorierte Dateien - + Collapse tree Baum zuklappen - + &%1 %2 &%1 %2 - + Recent Databases Vorherige Datenbanken - + Open Database Datenbank öffnen - + No previous databases available. Keine vorherigen geöffneten Datenbanken verfügbar. - + Database Datenbank - + Ctrl+Shift+O Strg+Shift+O - + Loaded database: %1 Geladene Datenbank: %1 - + Select your database... Wählen Sie eine Datenbank aus... - + monotone Databases (*.mtn *.db) monotone-Datenbanken (*.mtn *.db) - + Ctrl+B Strg+B - + Changeset browser Änderungen-Browser - + Close Schließen - + Alt+A Alt+A - + Alt+C Alt+C - + Alt+P Alt+P - + Alt+N Alt+N - - Currently there is no workspace or database loaded. - -To open a workspace, go to File > Open workspace -or File > Open Database for a database respectively. - Derzeit ist weder ein Arbeitsbereich noch eine Datenbank geladen, - -Um einen Arbeitsbereich zu öffnen, gehen Sie auf Datei > Arbeitsbereich öffnen -oder respektive auf Datei > Datenbank öffnen. - - - + Window Fenster - + Alt+D Alt+D - + Alt+R Alt+R - + Alt+M Alt+M - + Alt+U Alt+U - + Update workspace Arbeitsbereich aktualisieren - + Ctrl+U Strg+U - + Alt+I Alt+I - + Checkout revision Revision auschecken - + Ctrl+Shift+U Strg+Umschalt+U - + Bring all to front Alle nach vorne bringen - + %1 - database mode - guitone %1 - Datenbankmodus - guitone - + %1 - workspace mode - guitone %1 - Arbeitsbereichmodus - guitone - - No workspace or database loaded - guitone - Kein Arbeitsbereich oder Datenbank geladen - guitone - - - + Unable to load workspace Konnte Arbeitsbereich nicht laden - + The workspace '%1' could not be loaded. monotone returned: @@ -1582,12 +1534,12 @@ monotone gab zurück: %2 - + Commit revision Revision einpflegen - + Ctrl+C Strg+C @@ -1608,12 +1560,12 @@ Die letzte Ausgabe war: %1 - + Failed to load database Konnte Datenbank nicht laden - + The database could not be loaded. The last output was: @@ -1624,25 +1576,45 @@ Die letzte Ausgabe war: %1 - + Preferences... Einstellungen... - + Check for updates Auf Aktualisierungen prüfen - + No updates available Keine Aktualisierungen verfügbar - + Your version of guitone (%1) is already up-to-date. Ihre Version von guitone (%1) ist bereits aktuell. + + + Reload workspace + Arbeitsbereich neu laden + + + + Find unaccounted renames + Finde nicht verfolgte Umbenennungen + + + + Nothing found + Nichts gefunden + + + + No unaccounted renames found for this workspace. + Keine nicht verfolgten Umbenennungen in diesem Arbeitsbereich gefunden. + Manifest @@ -1683,18 +1655,7 @@ Die letzte Ausgabe war: Monotone - - The monotone process exited unexpectedly (return code %1). Please reconfigure the path to the monotone binary in the Preferences dialog or check if the version of the database you try to load matches the monotone version you are using. - -monotone returned: -%2 - Der monotone-Prozess wurde unerwartet beendet (Rückgabewert %1). Bitte überprüfen Sie den Pfad zur ausführbaren Datei von monotone im Eigenschaften-Dialog und stellen Sie sicher, dass die Version der Datenbank, die Sie versucht haben zu laden, mit der Version von monotone übereinstimmt. - -monotone gab zurück: -%2 - - - + [process not created] [Prozess nicht erzeugt] @@ -1702,7 +1663,7 @@ monotone gab zurück: MonotoneDelegate - + [unknown branch] [unbekannter Zweig] @@ -1710,60 +1671,60 @@ monotone gab zurück: Preferences - - Low (fatal errors) - Niedrig (fatale Fehler) - - - - Medium (critical errors) - Mittel (kritische Fehler) - - - - High (warnings) - Hoch (Warnungen) - - - - All (debug messages) - Alle (Debugmeldungen) - - - + Error Fehler - + The path to the monotone binary is either invalid or points to an older version of monotone. Guitone requires monotone version %1 or a monotone with interface version %2 or later. Der Pfad zur ausführbaren Datei von monotone ist entweder ungültig oder zeigt auf eine ältere Version von monotone. Guitone benötigt monotone Version %1 oder ein monotone mit einer Interface-Version %2 oder neuer. - + Notice Hinweis - + You need to reload your current workspace or restart guitone to use the new monotone binary. Sie müssen Ihren derzeitigen Arbeitsbereich oder guitone neu laden, um die neue monotone-Binary zu nutzen. - + Choose the monotone executable Wählen Sie die ausführbare Datei von monotone - + Binaries (mtn mtn.exe) Binärdateien (mtn mtn.exe) - - The path to the monotone binary is either invalid or points to a version of monotone with which guitone can't work with. Guitone requires monotone with an interface version between %2 and %3. You can check the interface version of your monotone installation by executing `mtn automate interface_version`. - Der Pfad zur ausführbaren Datei von monotone ist entweder ungültig oder zeigt auf eine Version von monotone, mit der guitone nicht arbeiten kann. Guitone benötigt ein monotone mit einer Interface-Version zwischen %2 und %3. Sie können die Interface-Version Ihrer monotone-Installation durch den Aufruf des Kommandos `mtn automate interface_version` in Erfahrung bringen. + + Very low (only fatal) + Sehr niedrig (nur fatale Fehler) + + + Low (critical) + Niedrig (kritische Fehler) + + + + Medium (warnings) + Mittel (Warnungen) + + + + High (info messages) + Hoch (Informationen) + + + + Very high (debug messages) + Sehr hoch (Debug-Meldungen) + PreferencesDialog @@ -1803,12 +1764,12 @@ monotone gab zurück: Log-Level - + OK OK - + Cancel Abbrechen @@ -1861,43 +1822,21 @@ monotone gab zurück: Attr gelöscht - - %1 to %2 - %1 nach %2 - - - - '%1' from %2 - '%1' von %2 - - - - '%1' to '%2' for %3 - '%1' auf '%2' für %3 - - - + %1 to %2 %1 nach %2 - + '%1' from %2 '%1' von %2 - - '%1' to '%2' -for %3 - '%1' auf '%2' -für %3 - - - + '%1' to '%2' for %3 @@ -2054,12 +1993,12 @@ für %3 Unterschiede anzeigen - + OK OK - + Cancel Abbrechen @@ -2137,27 +2076,73 @@ für %3 Tags - + Name Name - + Revision Revision - + Signer Unterschreiber - + Branches Zweige + UnaccountedRenames + + + Missing paths and possible rename targets + Fehlende Pfade und mögliche Ziele + + + + File ID (if applicable) + Datei-ID (sofern anwendbar) + + + + n/a + n/a + + + + matched by name + passt auf Grund des Names + + + + matched by content + passt auf Grund des Inhalts + + + + UnaccountedRenamesDialog + + + Unaccounted renames + Nicht verfolgte Umbenennungen + + + + Perform checked renames + Benenne markierte um + + + + Close + Schließen + + + UpdateWorkspace @@ -2254,11 +2239,6 @@ für %3 - The selected revision has more than one branch cert attached - please select one: - Die ausgewählte Revision besitzt mehr als ein Zweigzertifikat - bitte wählen Sie eins: - - - The selected revision has more than one branch cert attached - please select one: Die ausgewählte Revision besitzt mehr als ein ============================================================ --- src/Guitone.cpp cae5d0b8210527a4327743b7ea8c4993d4e7639a +++ src/Guitone.cpp 444b0af3793794d34e4657b2fea5e5e983d90ae2 @@ -26,7 +26,6 @@ #ifdef Q_WS_MACX #include "CocoaUtil.h" -#include #endif #include @@ -34,107 +33,18 @@ #include #include #include +#include -#ifdef Q_WS_MACX -static OSErr OpenDocumentAE(const AppleEvent * evt, AppleEvent *, long) -{ - AEDescList docList; - DescType retType; - AEKeyword keywd; - FSRef fsref; - long count, size; - - AEGetParamDesc(evt, keyDirectObject, typeAEList, &docList); - AECountItems(&docList, &count); - - OSErr err; - - for (int i = 1; i <= count; i++) - { - err = AEGetNthPtr(&docList, i, typeFSRef, &keywd, - &retType, (Ptr)&fsref, sizeof(fsref), &size); - - if (err != noErr) - { - W("Couldn't retrieve FSRef."); - continue; - } - - QString path = CocoaUtil::FSRefToPath(fsref); - if (path.isEmpty()) - { - W("Couldn't convert FSRef to path."); - continue; - } - - D(QString("Acquired new path: %1").arg(path)); - APP->loadFromPath(path); - } - AEDisposeDesc(&docList); - - return 0; -} - -bool Guitone::macEventFilter(EventHandlerCallRef caller, EventRef event) -{ - // - // taken from - // http://developer.apple.com/documentation/AppleScript/Conceptual/ - // AppleEvents/dispatch_aes_aepg/chapter_4_section_3.html - // - Boolean release = false; - EventRecord eventRecord; - OSErr ignoredErr; - - // Events of type kEventAppleEvent must be removed from the queue - // before being passed to AEProcessAppleEvent. - if (IsEventInQueue(GetMainEventQueue(), event)) - { - // RemoveEventFromQueue will release the event, which will - // destroy it if we don't retain it first. - RetainEvent(event); - release = true; - RemoveEventFromQueue(GetMainEventQueue(), event); - } - - // Convert the event ref to the type AEProcessAppleEvent expects. - ConvertEventRefToEventRecord(event, &eventRecord); - ignoredErr = AEProcessAppleEvent(&eventRecord); - - if (release) - ReleaseEvent(event); - - // This Carbon event has been handled, even if no AppleEvent handlers - // were installed for the Apple event. - return noErr; -} -#endif - Guitone::Guitone(int argc, char** argv) - : QApplication(argc, argv), updateDialog(0) + : QApplication(argc, argv), updateDialog(0), somethingLoaded(false) { setQuitOnLastWindowClosed(false); setOrganizationName("Thomas Keller"); setOrganizationDomain("thomaskeller.biz"); setApplicationName("guitone"); - -#ifdef Q_WS_MACX - // install apple event handler to open documents - OSErr err = AEInstallEventHandler( - kCoreEventClass, kAEOpenDocuments, - NewAEEventHandlerUPP(OpenDocumentAE), 0, false - ); - require_noerr(err, error_installing_event_handler); - return; -error_installing_event_handler: - C("Could not install OpenDocumentAE event handler"); -#endif -} - -bool Guitone::init() -{ + // check for updates immediatly on launch if (Settings::getBool("CheckForUpdates", true)) { #ifdef Q_WS_MACX @@ -148,13 +58,77 @@ bool Guitone::init() } #endif } + + // + // Try to load something given on the command line + // or found in one of the recent items lists + // + // FIXME: This is disabled for OSX since it interfers terribly with + // FileOpen events on this platform, i.e. we can't determine + // properly if the program is opened and shortly afterwards gets + // a FileOpen event or if there will no such event and we have to look + // at the command line or in the recent lists for something to load. + // + // What we're doing instead here for Mac OS X (whose users would otherwise + // fail to use guitone by opening it without also opening a workspace or + // database) is to create a global QMenuBar (MacStartMenu), which contains + // the basic file menu entries to open (recent) workspaces and databases + // amongst regular stuff like about dialogs and the update stuff. This is + // certainly not the end for "load recent automatically" on Mac, but a + // workaround. + // +#ifdef Q_WS_MACX + macStartMenu = new MacStartMenu(); + connect( + macStartMenu, SIGNAL(loadWorkspace(const QString &)), + this, SLOT(loadWorkspace(const QString &)) + ); + + connect( + macStartMenu, SIGNAL(loadDatabase(const QString &)), + this, SLOT(loadDatabase(const QString &)) + ); +#else + QTimer::singleShot(0, this, SLOT(loadSomething())); +#endif +} + +Guitone::~Guitone() +{ + I(openWindows.size() == 0); + I(monotoneInstances.size() == 0); + if (updateDialog) delete updateDialog; + +#ifdef Q_WS_MACX + disconnect( + macStartMenu, SIGNAL(loadWorkspace(const QString &)), + this, SLOT(loadWorkspace(const QString &)) + ); + + disconnect( + macStartMenu, SIGNAL(loadDatabase(const QString &)), + this, SLOT(loadDatabase(const QString &)) + ); + + delete macStartMenu; +#endif +} + +#ifndef Q_WS_MACX +void Guitone::loadSomething() +{ QStringList args = arguments(); - bool somethingLoaded = false; - for (int i=1, j=args.size(); i 0 && loadWorkspace(workspaces.at(0)); } if (!somethingLoaded) { + D("trying to load recent database"); QStringList databases = Settings::getItemList("RecentDatabaseList"); somethingLoaded = databases.size() > 0 && loadDatabase(databases.at(0)); } @@ -175,21 +151,67 @@ bool Guitone::init() // if still nothing is loaded, prompt the user to load a workspace if (!somethingLoaded) { + D("prompting for a workspace"); QString workspaceDir = QFileDialog::getExistingDirectory(0, tr("Select your workspace...")); somethingLoaded = !workspaceDir.isEmpty() && loadWorkspace(workspaceDir); } // if nothing could be loaded (= no window could be created), - // let the app close itself - return somethingLoaded; + // quit the application + if (!somethingLoaded) + { + D("nothing could be loaded, quitting"); + quit(); + } } +#endif -Guitone::~Guitone() +// this code is borrowed and adapted from QDesigner - thanks to the Trolls! +bool Guitone::event(QEvent * ev) { - I(openWindows.size() == 0); - I(monotoneInstances.size() == 0); - if (updateDialog) delete updateDialog; + bool eaten; + switch (ev->type()) + { + case QEvent::FileOpen: + { + QApplication::processEvents(); + D("got load event"); + if (loadFromPath(static_cast(ev)->file())) + { + QApplication::processEvents(); + D("loading from path succeeded"); + somethingLoaded = true; + } + eaten = true; + break; + } + case QEvent::Close: + { + D("got close event"); + QCloseEvent * closeEvent = static_cast(ev); + + // try to close all windows, if any window refuses to be closed, + // just eat the event and do nothing + if (closeAllWindows()) + { + closeEvent->setAccepted(true); + eaten = QApplication::event(ev); + D("closing all windows succeeded"); + } + else + { + closeEvent->setAccepted(false); + eaten = true; + D("could not close all windows"); + } + break; + } + default: + eaten = QApplication::event(ev); + break; + } + return eaten; } bool Guitone::loadFromPath(const QString & path) @@ -385,7 +407,7 @@ const QList Guitone::window return openWindows; } -void Guitone::quit() +bool Guitone::closeAllWindows() { // do we need to close any windows? if (openWindows.size() > 0) @@ -397,11 +419,16 @@ void Guitone::quit() // problems foreach (MainWindow * wnd, openWindows) { - wnd->close(); + // QWidget::close() returns true if the widget could be closed, + // i.e. hasn't ignored the close event + if (!wnd->close()) return false; } - return; } - + return true; +} + +void Guitone::quit() +{ Settings::sync(); QApplication::quit(); } ============================================================ --- src/Guitone.h 881dffddd5bdd9999f2ed36ecdcaa019de153cb4 +++ src/Guitone.h 9c9143ee93f0633494e48c42e0ee0f64d6af46d6 @@ -24,7 +24,12 @@ #include "MainWindow.h" #include "Monotone.h" #include "ApplicationUpdate.h" +#include "DebugLog.h" +#ifdef Q_WS_MACX +#include "MacStartMenu.h" +#endif + #include #include #include @@ -37,7 +42,6 @@ public: public: Guitone(int, char**); ~Guitone(); - bool init(); const QList windowList() const; MainWindow * findMainWindow(QObject *); @@ -54,8 +58,11 @@ private slots: bool loadWorkspace(const QString &); bool loadDatabase(const QString &); void windowClosed(MainWindow *); +#ifndef Q_WS_MACX + void loadSomething(); +#endif void quit(); - + private: MainWindow * addWindow(); void removeWindow(MainWindow *); @@ -63,15 +70,20 @@ private: bool addMonotoneInstance(MainWindow *); bool removeMonotoneInstance(MainWindow *); -#ifdef Q_WS_MACX - bool macEventFilter(EventHandlerCallRef, EventRef); -#endif + bool closeAllWindows(); + + bool event(QEvent *); QList openWindows; QMap monotoneInstances; QMutex mutex; - ApplicationUpdate * updateDialog; + bool somethingLoaded; + +#ifdef Q_WS_MACX + MacStartMenu * macStartMenu; +#endif + }; #endif ============================================================ --- src/main.cpp e36e3f6a9adf493840944a11b718bf25abe079de +++ src/main.cpp b3c466a5103800420aa2b0cab6c9d1be4994f5c5 @@ -48,21 +48,30 @@ int main(int argc, char** argv) // install our own message handler to catch debug messages, warnings, etc. qInstallMsgHandler(guitoneMsgHandler); - // try to find a suitable locale and setup translations QString locale = QLocale::system().name(); - QTranslator translator; + + // load the default Qt translations + QTranslator qtTranslator; + if (!qtTranslator.load("qt_" + locale)) + { + W(QString("Couldn't load qt translation for %1 - using default") + .arg(locale)); + } + + // try to load the application's translation + QTranslator appTranslator; QString transFileName("guitone_" + locale); - if (!translator.load(transFileName, ":/i18n")) + if (!appTranslator.load(transFileName, ":/i18n")) { - W(QString("Couldn't load translation file for %1 - using default") + W(QString("Couldn't load app translation for %1 - using default") .arg(locale)); } Guitone app(argc, argv); - app.installTranslator(&translator); - if (!app.init()) return 1; + app.installTranslator(&appTranslator); + app.installTranslator(&qtTranslator); return app.exec(); } ============================================================ --- src/model/Certs.cpp 0158c7df5e847ce46b6b56557ea7647d5b423b52 +++ src/model/Certs.cpp e4a81c6408c64be1a305aa5eb70842e589e34311 @@ -91,7 +91,7 @@ void Certs::parseOutput() if (entry.sym == "value") { I(entry.vals.size() == 1); - cert.value = entry.vals.at(0); + cert.value = entry.vals.at(0).trimmed(); continue; } @@ -149,8 +149,14 @@ QVariant Certs::data(const QModelIndex & return QVariant(); } + if (role == Qt::TextAlignmentRole) + { + Qt::Alignment flags = Qt::AlignLeft | Qt::AlignTop; + return QVariant(flags); + } + if (role != Qt::DisplayRole) - { + { return QVariant(); } ============================================================ --- src/model/ContentDiff.cpp af2b8ba1408305c6413d2b0900bc9f7d1e76cb3c +++ src/model/ContentDiff.cpp 330cf8f074e03c6fc73d6581933660f2e5ac411c @@ -39,18 +39,8 @@ ContentDiff::~ContentDiff() delete mtnDelegate; } -bool ContentDiff::readDiff(QString fileName) -{ - return readDiff(fileName, QString(), QString()); -} - -bool ContentDiff::readDiff(QString fileName, QString leftRevision) -{ - return readDiff(fileName, leftRevision, QString()); -} - bool ContentDiff::readDiff( - QString fileName, QString leftRevision, QString rightRevision + const QString & fileName, const QString & base, const QString & target ) { // reset the view @@ -61,16 +51,18 @@ bool ContentDiff::readDiff( QStringList opts; - if (leftRevision.length() == 0) + if (base.isEmpty()) { - leftRevision = MonotoneDelegate::getBaseWorkspaceRevision(this); + opts << "r" << MonotoneDelegate::getBaseWorkspaceRevision(this); } + else + { + opts << "r" << base; + } - opts << "r" << leftRevision; - - if (rightRevision.length() > 0) + if (!target.isEmpty()) { - opts << "r" << rightRevision; + opts << "r" << target; } return mtnDelegate->triggerCommand(cmd, opts); ============================================================ --- src/model/ContentDiff.h 91ee6ba1a90506ce0890d45c4f55d9b146b2d871 +++ src/model/ContentDiff.h 9f0b45bde2c813734bf6f4a9a5c8cb88e629a41d @@ -76,9 +76,7 @@ public slots: inline FileDiffs getAllDiffs() { return diffParser->getAllDiffs(); } public slots: - bool readDiff(QString); - bool readDiff(QString, QString); - bool readDiff(QString, QString, QString); + bool readDiff(const QString &, const QString & baseRev = QString(), const QString & targetRev = QString()); signals: void diffRead(); ============================================================ --- src/model/Attributes.cpp 2ea5d72737c95823852fbf7038235b8af7349d49 +++ src/model/GetAttributes.cpp c1f8ccabf73da0b5f91634619197f0adbfeb31de @@ -18,59 +18,51 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -#include "Attributes.h" -#include "InventoryItem.h" +#include "GetAttributes.h" #include "Monotone.h" #include "BasicIOParser.h" -#include +#include +#include -Attributes::Attributes(QObject *parent) +GetAttributes::GetAttributes(QObject *parent) : QAbstractItemModel(parent) { attributes = new AttributeList(); mtnDelegate = new MonotoneDelegate(this); } -Attributes::~Attributes() +GetAttributes::~GetAttributes() { attributes->clear(); delete attributes; delete mtnDelegate; } -bool Attributes::readAttributes(const QModelIndex & index) +void GetAttributes::revert() { - // convert ProxyModel index to real Model index - QModelIndex sourceIndex = static_cast(index.model())->mapToSource(index); - // get the Model's item reference out there - InventoryItem *item = static_cast(sourceIndex.internalPointer()); + attributes->clear(); + path = QString(); + reset(); +} +bool GetAttributes::readAttributes(const QString & p) +{ // clear current attributes list attributes->clear(); // reset the view reset(); - if (item->isRootDirectory() || item->isCdUp()) - { - D("item is pseudo item (root or cdup)"); - return false; - } - - if (item->hasStatus(InventoryItem::Dropped) || !item->isTracked()) - { - D("item is not tracked or dropped"); - return false; - } - QStringList cmd; cmd.append("get_attributes"); - cmd.append(item->getPath()); + cmd.append(p); + path = p; + return mtnDelegate->triggerCommand(cmd); } -void Attributes::parseOutput() +void GetAttributes::parseOutput() { BasicIOParser parser(AutomateCommand::data); I(parser.parse()); @@ -143,77 +135,122 @@ void Attributes::parseOutput() emit attributesRead(); } -int Attributes::columnCount(const QModelIndex &parent) const +int GetAttributes::columnCount(const QModelIndex &parent) const { return 3; } -QVariant Attributes::data(const QModelIndex &index, int role) const +QVariant GetAttributes::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } - if (role != Qt::DisplayRole) + int row = index.row(), col = index.column(); + if (row >= attributes->size()) return QVariant(); + + Attribute attr = attributes->at(row); + + if (role == Qt::DisplayRole) { - return QVariant(); + switch (col) + { + case 1: return QVariant(attr.key.length() == 0 ? tr("empty") : attr.key); + case 2: return QVariant(attr.value.length() == 0 ? tr("empty") : attr.value); + default: return QVariant(); + } } - - int row = index.row(); - if (row >= attributes->size()) return QVariant(); - Attribute attr = attributes->at(row); + if (role == Qt::ToolTipRole) + { + switch (attr.state) + { + case Attribute::Added: return QVariant(tr("added")); + case Attribute::Dropped: return QVariant(tr("dropped")); + case Attribute::Changed: return QVariant(tr("changed")); + case Attribute::Unchanged: return QVariant(tr("unchanged")); + default: return QVariant(); + } + } - switch (index.column()) + if (role == Qt::DecorationRole && col == 0) { - case 0: return QVariant(attr.key); - case 1: return QVariant(attr.value); - case 2: - switch (attr.state) - { - case Attribute::Added: return QVariant(tr("added")); - case Attribute::Dropped: return QVariant(tr("dropped")); - case Attribute::Changed: return QVariant(tr("changed")); - case Attribute::Unchanged: return QVariant(tr("unchanged")); - } + switch (attr.state) + { + case Attribute::Added: return QVariant(QIcon(":/icons/green_dot.png")); + case Attribute::Dropped: return QVariant(QIcon(":/icons/red_dot.png")); + case Attribute::Unchanged: return QVariant(QIcon(":/icons/yellow_dot.png")); + case Attribute::Changed: return QVariant(QIcon(":/icons/blue_dot.png")); + default: return QVariant(); + } } + if (role == Qt::FontRole) + { + QFont font; + font.setItalic(true); + + if (col == 1 && attr.key.length() == 0) + return QVariant(font); + + if (col == 2 && attr.value.length() == 0) + return QVariant(font); + + return QVariant(); + } + + if (role == Qt::UserRole) + { + switch (col) + { + case 1: return QVariant(attr.key); + case 2: return QVariant(attr.value); + default: return QVariant(); + } + } + return QVariant(); } -Qt::ItemFlags Attributes::flags(const QModelIndex &index) const +Qt::ItemFlags GetAttributes::flags(const QModelIndex &index) const { - // TODO: add ItemIsEditable as soon as we can set/drop attributes through - // the automation interface, implement setData() then as well return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } -QVariant Attributes::headerData(int section, Qt::Orientation orientation, int role) const +QVariant GetAttributes::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + I(orientation == Qt::Horizontal); + + if (role == Qt::DisplayRole) { switch (section) { - case 0: return QVariant(tr("Key")); - case 1: return QVariant(tr("Value")); - case 2: return QVariant(tr("State")); + case 1: return QVariant(tr("Key")); + case 2: return QVariant(tr("Value")); + default: return QVariant(); } } + + if (role == Qt::DecorationRole && section == 0) + { + return QVariant(QIcon(":/icons/arrow_down.png")); + } + return QVariant(); } -int Attributes::rowCount(const QModelIndex& parent) const +int GetAttributes::rowCount(const QModelIndex& parent) const { return attributes->size(); } -QModelIndex Attributes::index(int row, int column, const QModelIndex& parent) const +QModelIndex GetAttributes::index(int row, int column, const QModelIndex& parent) const { return hasIndex(row, column, parent) ? createIndex(row, column, 0) : QModelIndex(); } -QModelIndex Attributes::parent(const QModelIndex& index) const +QModelIndex GetAttributes::parent(const QModelIndex& index) const { return QModelIndex(); } ============================================================ --- src/model/Attributes.h a4d4844a547daa12380f96da44609c627e859b25 +++ src/model/GetAttributes.h f9f06f87a1177277bce55b69800a14df55fffbd3 @@ -18,11 +18,9 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -// TODO: enable editing of items as well as dropping/adding them +#ifndef GETATTRIBUTES_H +#define GETATTRIBUTES_H -#ifndef ATTRIBUTES_H -#define ATTRIBUTES_H - #include "AutomateCommand.h" #include "MonotoneDelegate.h" @@ -35,13 +33,15 @@ typedef QList AttributeList; } Attribute; typedef QList AttributeList; -class Attributes : public QAbstractItemModel, public AutomateCommand +class GetAttributes : public QAbstractItemModel, public AutomateCommand { Q_OBJECT public: - Attributes(QObject*); - virtual ~Attributes(); - + GetAttributes(QObject*); + virtual ~GetAttributes(); + + inline QString currentPath() const { return path; } + // needed Qt Model methods QVariant data(const QModelIndex&, int) const; Qt::ItemFlags flags(const QModelIndex&) const; @@ -49,11 +49,12 @@ public: QModelIndex index(int, int, const QModelIndex&) const; QModelIndex parent(const QModelIndex&) const; int rowCount(const QModelIndex&) const; - int columnCount(const QModelIndex&) const; + int columnCount(const QModelIndex&) const; public slots: - bool readAttributes(const QModelIndex &); - + bool readAttributes(const QString &); + void revert(); + signals: void attributesRead(); @@ -61,6 +62,7 @@ private: void parseOutput(); AttributeList * attributes; MonotoneDelegate * mtnDelegate; + QString path; }; #endif ============================================================ --- src/model/GetFile.cpp 5151a9eccdcf6f92a140dc662d5c845f79484c34 +++ src/model/GetFile.cpp 54bfdc61b73d50418793fca0b86bc05f434ee311 @@ -33,23 +33,25 @@ GetFile::~GetFile() delete mtnDelegate; } -bool GetFile::readFileByName(QString fileName) +bool GetFile::readFileByName(const QString & fileName, const QString & rev) { - return readFileByName(fileName, MonotoneDelegate::getBaseWorkspaceRevision(this)); -} - -bool GetFile::readFileByName(QString fileName, QString revision) -{ QStringList cmd; cmd << "get_file_of" << fileName; QStringList opts; - opts << "r" << revision; + if (rev.isEmpty()) + { + opts << "r" << MonotoneDelegate::getBaseWorkspaceRevision(this); + } + else + { + opts << "r" << rev; + } return readFile(cmd, opts); } -bool GetFile::readFileById(QString fileID) +bool GetFile::readFileById(const QString & fileID) { QStringList cmd; cmd << "get_file" << fileID; @@ -130,6 +132,75 @@ void GetFile::applyDiff(Diff * diff) reset(); } +QModelIndex GetFile::getNextGroup(const QModelIndex & base, bool recurse) +{ + int startRow = 0; + ContentLine::Marker lastMarker = ContentLine::Unchanged; + if (base.isValid() && base.row() < fileContents.size()) + { + startRow = base.row(); + lastMarker = fileContents.at(startRow)->marker; + } + + for (int i=startRow, j=fileContents.size(); imarker != lastMarker && line->marker != ContentLine::Unchanged) + { + return index(i, 0, QModelIndex()); + } + lastMarker = line->marker; + } + + // if we already recursed, stop here + if (recurse) return QModelIndex(); + // try to get the first group + return getNextGroup(QModelIndex(), true); +} + +QModelIndex GetFile::getPrevGroup(const QModelIndex & base, bool recurse) +{ + int startRow = fileContents.size() - 1; + ContentLine::Marker lastMarker = ContentLine::Unchanged; + if (base.isValid() && base.row() < fileContents.size()) + { + startRow = base.row(); + lastMarker = fileContents.at(startRow)->marker; + } + + bool foundLast = false; + for (int i=startRow; i>=0; i--) + { + ContentLine * line = fileContents.at(i); + + // did we already found the last line of a group of similar lines? + if (!foundLast) + { + if (line->marker != lastMarker && line->marker != ContentLine::Unchanged) + { + foundLast = true; + } + lastMarker = line->marker; + continue; + } + + if (foundLast) + { + // if the marker just changed, the previous line was our + // first line + if (line->marker != lastMarker) + { + return index(i + 1, 0, QModelIndex()); + } + } + } + + // if we've already recursed, stop here + if (recurse) return QModelIndex(); + // try to get the last group + return getPrevGroup(QModelIndex(), true); +} + int GetFile::columnCount(const QModelIndex &parent) const { return 2; ============================================================ --- src/model/GetFile.h 2542dad0537061ec09b64b453ac07a972788b1c6 +++ src/model/GetFile.h 341b069c5778068d50bbf01cd34557a04b639db3 @@ -53,11 +53,12 @@ public: QModelIndex parent(const QModelIndex&) const; int rowCount(const QModelIndex&) const; int columnCount(const QModelIndex&) const; + QModelIndex getNextGroup(const QModelIndex &, bool recurse = false); + QModelIndex getPrevGroup(const QModelIndex &, bool recurse = false); public slots: - bool readFileById(QString); - bool readFileByName(QString); - bool readFileByName(QString, QString); + bool readFileById(const QString &); + bool readFileByName(const QString &, const QString & rev = QString()); void applyDiff(Diff * diff); signals: ============================================================ --- src/model/GetRevision.cpp a953b589dc8091738c22564d6c18ff070eb48211 +++ src/model/GetRevision.cpp 1ca82e3b9ba7a2956fc60cff0e976c0ec1e681e2 @@ -85,15 +85,15 @@ void GetRevision::parseOutput() if (j == 0 && entry.sym == "new_manifest") { - I(entry.vals.size() == 1); - revision.new_manifest = entry.vals.at(0); + I(!entry.hash.isNull()); + revision.new_manifest = entry.hash; break; } if (entry.sym == "old_revision") { - I(entry.vals.size() == 1); - currentRevision = entry.vals.at(0); + I(!entry.hash.isNull()); + currentRevision = entry.hash; QList changelist; revision.changesAgainstParent.insert(currentRevision, changelist); break; ============================================================ --- src/model/GetRevision.h 0308fe66ff1749162131c693e28efe7497579d55 +++ src/model/GetRevision.h 9c2675f10e96cf957979a04a2df71ae989d1d6c6 @@ -63,50 +63,6 @@ struct Change { return Qt::transparent; } - inline QString escape(const QString & in) - { - QString out(in); - out.replace("\\", "\\\\"); - out.replace("\"", "\\\""); - return out; - } - - inline QString getStanzaData() - { - switch (type) - { - case AddDir: - return QString("add_dir \"%1\"") - .arg(escape(stanza.at(0).vals.at(0))); - case AddFile: - return QString("add_file \"%1\"\ncontent [%2]") - .arg(escape(stanza.at(0).vals.at(0))) - .arg(stanza.at(1).vals.at(0)); - case Delete: - return QString("delete \"%1\"") - .arg(escape(stanza.at(0).vals.at(0))); - case Patch: - return QString("patch \"%1\"\nfrom [%2]\nto [%3]") - .arg(escape(stanza.at(0).vals.at(0))) - .arg(stanza.at(1).vals.at(0)) - .arg(stanza.at(2).vals.at(0)); - case Rename: - return QString("rename \"%1\"\nto \"%2\"") - .arg(escape(stanza.at(0).vals.at(0))) - .arg(escape(stanza.at(1).vals.at(0))); - case AttrClear: - return QString("clear \"%1\"\nattr \"%2\"") - .arg(escape(stanza.at(0).vals.at(0))) - .arg(escape(stanza.at(1).vals.at(0))); - case AttrSet: - return QString("set \"%1\"\nattr \"%2\"\nvalue \"%3\"") - .arg(stanza.at(0).vals.at(0)) - .arg(stanza.at(1).vals.at(0)) - .arg(stanza.at(2).vals.at(0)); - } - return QString(); - } - inline QString getDisplayData() { switch (type) ============================================================ --- src/model/Inventory.cpp fe03f18135e1c1a39e069ea74b3f6f36028156e5 +++ src/model/Inventory.cpp 38d53b03b3303315ba61b72f5241ffa81e24f8f3 @@ -27,10 +27,10 @@ Inventory::Inventory(QObject *parent) : Inventory::Inventory(QObject *parent) : QAbstractItemModel(parent) { - // create a dummy item since the view needs at least one item - // in the model, otherwise the app crashes - rootItem = new InventoryItem(); - regex = new QRegExp("^(R|D|[ ])(R|A|[ ])(M|P|U|I|[ ])\\s(\\d+)\\s(\\d+)\\s(.+)$"); + // create a dummy item since the view needs at least one item + // in the model, otherwise the app crashes + rootItem = new InventoryItem(); + regex = new QRegExp("^(R|D|[ ])(R|A|[ ])(M|P|U|I|[ ])\\s(\\d+)\\s(\\d+)\\s(.+)$"); regex->setMinimal(true); mtnDelegate = new MonotoneDelegate(this); @@ -43,8 +43,8 @@ Inventory::~Inventory() Inventory::~Inventory() { - delete rootItem; - delete regex; + delete rootItem; + delete regex; delete mtnDelegate; } @@ -53,74 +53,80 @@ bool Inventory::readInventory() QStringList cmd; cmd << "inventory"; - return mtnDelegate->triggerCommand(cmd); + return mtnDelegate->triggerCommand(cmd); } void Inventory::parseOutput() { - QStringList lines = AutomateCommand::data.split("\n", QString::SkipEmptyParts); - QMap renameMap; - QMap::iterator renameIter; + QStringList lines = AutomateCommand::data.split("\n", QString::SkipEmptyParts); + QMap renameMap; + QMap::iterator renameIter; - InventoryItem *item; - QList tempItems; - int status(0); - int from_id(0); - int to_id(0); - QString path(""); - bool isDirectory(false); + QList items; + InventoryItem * item; + + int status(0); + int from_id(0); + int to_id(0); + QString path(""); + bool isDirectory(false); - for (QStringList::Iterator it = lines.begin(); it != lines.end(); ++it) - { - if (!parseInventoryLine(*it, status, from_id, to_id, path, isDirectory)) - { - continue; - } - - // this item is given a parent explicitely later on - item = new InventoryItem(isDirectory); + for (QStringList::Iterator it = lines.begin(); it != lines.end(); ++it) + { + if (!parseInventoryLine(*it, status, from_id, to_id, path, isDirectory)) + { + continue; + } + + // this item is given a parent explicitely later on + item = new InventoryItem(isDirectory); item->setPath(path); item->setStatus(status); - if (from_id > 0) - { - renameMap[-from_id] = item; - } - if (to_id > 0) - { - renameMap[to_id] = item; - } + if (from_id > 0) + { + renameMap[-from_id] = item; + } + if (to_id > 0) + { + renameMap[to_id] = item; + } - tempItems.push_back(item); - } + items.push_back(item); + } - int id = 0; + int id = 0; - while (true) - { - renameIter = renameMap.find(++id); - if (renameIter == renameMap.end()) break; + while (true) + { + renameIter = renameMap.find(++id); + if (renameIter == renameMap.end()) break; - renameMap[id]->setRenamedFrom(renameMap[-id]); - renameMap[-id]->setRenamedTo(renameMap[id]); - } + renameMap[id]->setRenamedFrom(renameMap[-id]); + renameMap[-id]->setRenamedTo(renameMap[id]); + } + flatItemList.clear(); + flatItemList = items; + // FIXME: we shouldn't really add a workspace root item here, but // mtn automate inventory currently doesn't print the root workspace dir InventoryItem * branch = new InventoryItem(true, true); branch->setParent(rootItem); branch->setPath("."); branch->setStatus(0); - branch->setChildren(buildTreeRecursive(tempItems, NULL)); + branch->setChildren(buildTreeRecursive(items, NULL)); + // remove any older item + rootItem->deleteAllChildren(); rootItem->appendChild(branch); - // reset the model to repaint the view completly - // (all QModelIndexes are discarded through that, e.g. selections!) - reset(); + // reset the model to repaint the view completly + // (all QModelIndexes are discarded through that, e.g. selections!) + reset(); - // restore the normal cursor - qApp->restoreOverrideCursor(); + // restore the normal cursor + qApp->restoreOverrideCursor(); emit modelCreated(); } @@ -137,116 +143,115 @@ void Inventory::loadBranchName() #endif } -QList Inventory::buildTreeRecursive(QList &items, InventoryItem* parentItem) +QList Inventory::buildTreeRecursive(QList & items, InventoryItem * parentItem) { - QList finalItems; + QList finalItems; - QString parentPath = ""; - int parentStatus = 0; - - if (parentItem != NULL) - { - parentPath = parentItem->getPath(); - parentStatus = parentItem->getStatus(); - } + QString parentPath = ""; - // add pseudo item "cd up" for each directory + if (parentItem != NULL) + { + parentPath = parentItem->getPath(); + } + + // add pseudo item "cd up" for each directory level InventoryItem * cdUp = new InventoryItem(true); cdUp->setParent(parentItem); cdUp->setPath(parentPath + QString("/..")); + finalItems.append(cdUp); - items.prepend(cdUp); - InventoryItem * currentItem; - while (items.size() > 0) - { - currentItem = items.front(); + + while (items.size() > 0) + { + currentItem = items.front(); - // - // if the item is not directly inside the current path stop immediately - // - QString tmpPath = parentPath; - tmpPath.append("/"); - tmpPath.append(currentItem->getFilename()); + // + // if the item is not directly inside the current path stop immediately + // + QString tmpPath = parentPath; + tmpPath.append("/"); + tmpPath.append(currentItem->getFilename()); - // path may not be empty for this condition - if (!parentPath.isEmpty() && currentItem->getPath().compare(tmpPath) != 0) - { - break; - } + // path may not be empty for this condition + if (!parentPath.isEmpty() && currentItem->getPath().compare(tmpPath) != 0) + { + break; + } - // remove the item if we can append it now - items.pop_front(); + // remove the item if we can append it now + items.pop_front(); - // - // it seems to be a valid item - // + // + // it seems to be a valid item + // - // if the item is directory a directory, make sure we catch decendant items... recursion! - if (currentItem->isDirectory() && !currentItem->isCdUp()) - { - currentItem->setChildren(buildTreeRecursive(items, currentItem)); + // if the item is directory a directory, catch items inside it + if (currentItem->isDirectory()) + { + currentItem->setChildren(buildTreeRecursive(items, currentItem)); } - // append item to final list - finalItems.push_back(currentItem); + // append item to final list + finalItems.push_back(currentItem); - } - return finalItems; + } + + return finalItems; } QModelIndex Inventory::index(int row, int column, const QModelIndex &parent) const { - InventoryItem * parentItem; + InventoryItem * parentItem; - if (!parent.isValid()) - { - parentItem = rootItem; - } - else - { - parentItem = static_cast(parent.internalPointer()); - } + if (!parent.isValid()) + { + parentItem = rootItem; + } + else + { + parentItem = static_cast(parent.internalPointer()); + } - InventoryItem * childItem = parentItem->child(row); + InventoryItem * childItem = parentItem->child(row); - if (childItem) - { - return createIndex(row, column, childItem); - } + if (childItem) + { + return createIndex(row, column, childItem); + } - return QModelIndex(); + return QModelIndex(); } int Inventory::columnCount(const QModelIndex &parent) const { - if (parent.isValid()) - { - return static_cast(parent.internalPointer())->columnCount(); - } + if (parent.isValid()) + { + return static_cast(parent.internalPointer())->columnCount(); + } - return rootItem->columnCount(); + return rootItem->columnCount(); } QVariant Inventory::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - { - return QVariant(); - } + if (!index.isValid()) + { + return QVariant(); + } - InventoryItem * item = static_cast(index.internalPointer()); + InventoryItem * item = static_cast(index.internalPointer()); - if ((role == Qt::DecorationRole) && (index.column() == 0)) - { + if ((role == Qt::DecorationRole) && (index.column() == 0)) + { IconProvider * provider = IconProvider::singleton(); - return provider->getIcon(item); - } - else - { - return item->data(index.column(), role); - } + return provider->getIcon(item); + } + else + { + return item->data(index.column(), role); + } } bool Inventory::setData(const QModelIndex & idx, const QVariant & value, int role) @@ -261,7 +266,7 @@ Qt::ItemFlags Inventory::flags(const QMo Qt::ItemFlags Inventory::flags(const QModelIndex &index) const { - if (!index.isValid()) return 0; + if (!index.isValid()) return 0; QFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; @@ -274,10 +279,10 @@ Qt::ItemFlags Inventory::flags(const QMo flags |= Qt::ItemIsEditable | Qt::ItemIsDragEnabled; if (item->isDirectory()) - { - flags |= Qt::ItemIsDropEnabled; + { + flags |= Qt::ItemIsDropEnabled; } - */ + */ return flags; } @@ -285,42 +290,42 @@ QVariant Inventory::headerData(int secti // the root item needs to store the header for each field QVariant Inventory::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation == Qt::Horizontal && role == Qt::DisplayRole) - { - return rootItem->data(section, role); - } + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + return rootItem->data(section, role); + } - return QVariant(); + return QVariant(); } QModelIndex Inventory::parent(const QModelIndex& index) const { - if (!index.isValid()) - { - return QModelIndex(); - } + if (!index.isValid()) + { + return QModelIndex(); + } - InventoryItem * childItem = static_cast(index.internalPointer()); - InventoryItem * parentItem = childItem->parent(); + InventoryItem * childItem = static_cast(index.internalPointer()); + InventoryItem * parentItem = childItem->parent(); - if (parentItem == rootItem) - { - return QModelIndex(); - } + if (parentItem == rootItem) + { + return QModelIndex(); + } - return createIndex(parentItem->row(), 0, parentItem); + return createIndex(parentItem->row(), 0, parentItem); } int Inventory::rowCount(const QModelIndex& parent) const { - InventoryItem * parentItem = rootItem; + InventoryItem * parentItem = rootItem; - if (parent.isValid()) - { - parentItem = static_cast(parent.internalPointer()); - } + if (parent.isValid()) + { + parentItem = static_cast(parent.internalPointer()); + } - return parentItem->childCount(); + return parentItem->childCount(); } bool Inventory::parseInventoryLine( @@ -403,19 +408,17 @@ bool Inventory::parseInventoryLine( // now determine if the file has been renamed from_id = list[4].toInt(); to_id = list[5].toInt(); - - // remove trailing slash - path = list[6].trimmed(); + path = list[6].trimmed(); isDirectory = false; - if (path.endsWith('/')) - { - isDirectory = true; - path = path.left(path.length() - 1); - } - - // parsing was successful - return true; + if (path.endsWith('/')) + { + isDirectory = true; + path = path.left(path.length() - 1); + } + + // parsing was successful + return true; } bool Inventory::handleError(int errCode) @@ -436,3 +439,107 @@ bool Inventory::handleError(int errCode) return true; } +QMap Inventory::findUnaccountedRenames() +{ + QList missingItems; + QList unknownItems; + + foreach (InventoryItem * item, flatItemList) + { + if (item->hasStatus(InventoryItem::Missing)) + { + missingItems.append(item); + } + + if (item->hasStatus(InventoryItem::Unknown)) + { + unknownItems.append(item); + } + } + + // TODO: progess bar here! + + QString parentRev = MonotoneDelegate::getBaseWorkspaceRevision(this); + FileEntryList parentList = + MonotoneDelegate::getRevisionManifest(this, parentRev); + + QMap unaccountedRenames; + QMap fileIds; + + foreach (InventoryItem * missingItem, missingItems) + { + FileEntryList candidates; + FileEntry entry; + + // this is a new entry not recorded in the base roster + if (missingItem->hasStatus(InventoryItem::Added) || + missingItem->hasStatus(InventoryItem::RenamedTo)) + { + entry.path = missingItem->getPath(); + entry.is_dir = missingItem->isDirectory(); + } + else + { + bool found = false; + foreach (entry, parentList) + { + if (entry.path == missingItem->getPath()) + { + found = true; + break; + } + } + I(found); + } + + foreach (InventoryItem * unknownItem, unknownItems) + { + QString unknownPath = unknownItem->getPath(); + + // calculate the file id of the unknown file + if (!unknownItem->isDirectory() && + !fileIds.contains(unknownPath)) + { + QString fileid = MonotoneDelegate::getFileId(this, unknownPath); + // file was not readable, etc. + if (fileid.isEmpty()) continue; + fileIds.insert(unknownPath, fileid); + } + + // at first do a simple file name check + if ((missingItem->getFilename() == unknownItem->getFilename()) && + (missingItem->isDirectory() == unknownItem->isDirectory())) + { + FileEntry can(unknownPath, unknownItem->isDirectory()); + if (fileIds.contains(unknownPath)) + { + can.fileid = fileIds.value(unknownPath); + } + candidates.append(can); + continue; + } + + // we can't do anything for directories from here on + if (missingItem->isDirectory() || unknownItem->isDirectory()) + continue; + + // we now rely on the fact that we have a fileid + I(fileIds.contains(unknownPath)); + + if (fileIds.value(unknownPath) == entry.fileid) + { + candidates.append(FileEntry(unknownPath, false, entry.fileid)); + } + } + + // only add those missing items to the map which are having any + // candidates, i.e. are likely to be renamed + if (candidates.size() > 0) + { + unaccountedRenames.insert(entry, candidates); + } + } + + return unaccountedRenames; +} + ============================================================ --- src/model/Inventory.h e9d25b6dda9dcb6b3678cb3699da93695f4ac1ca +++ src/model/Inventory.h c9834d77c69b3b9d7c6dc35a5a8bc6118a5b01ec @@ -37,9 +37,7 @@ class Inventory : public QAbstractItemMo Inventory(QObject *parent); ~Inventory(); bool readInventory(); - static QString getOption(QObject *, const QString &); - static QString getBranchName(QObject *); - static QString getBranchNameShort(QObject *); + QMap findUnaccountedRenames(); // needed Qt Model methods QVariant data(const QModelIndex&, int) const; @@ -55,17 +53,18 @@ class Inventory : public QAbstractItemMo private: void parseOutput(); bool handleError(int); - bool parseInventoryLine(const QString &, int &, int &, int &, QString &, bool &); - QList buildTreeRecursive(QList &, InventoryItem*); - - InventoryItem * rootItem; - QRegExp * regex; - MonotoneDelegate * mtnDelegate; - QString branchName; + bool parseInventoryLine(const QString &, int &, int &, int &, QString &, bool &); + QList buildTreeRecursive(QList &, InventoryItem*); + + InventoryItem * rootItem; + QRegExp * regex; + MonotoneDelegate * mtnDelegate; + QString branchName; + QList flatItemList; private slots: void loadBranchName(); - + signals: void modelCreated(); void invalidWorkspaceFormat(const QString &); ============================================================ --- src/model/Keys.cpp 9d8302773f28fa012ce8550b4afc166f5318790b +++ src/model/Keys.cpp 4fc764b9241d0fcdd8745c96a32dc7504ed26565 @@ -77,15 +77,15 @@ void Keys::parseOutput() if (entry.sym == "public_hash") { - I(entry.vals.size() == 1); - key.public_hash = entry.vals.at(0); + I(!entry.hash.isNull()); + key.public_hash = entry.hash; continue; } if (entry.sym == "private_hash") { - I(entry.vals.size() == 1); - key.private_hash = entry.vals.at(0); + I(!entry.hash.isNull()); + key.private_hash = entry.hash; continue; } ============================================================ --- src/model/Manifest.cpp 2fbdb523819234d538fa763d4affeb9d81a9af6f +++ src/model/Manifest.cpp e4700d858ea55077620324ec7c97cdb8286a876a @@ -114,8 +114,8 @@ void Manifest::parseOutput() if (entry.sym == "content") { - I(entry.vals.size() == 1); - mEntry->hash = entry.vals.at(0); + I(!entry.hash.isNull()); + mEntry->hash = entry.hash; continue; } ============================================================ --- src/model/Tags.cpp 031fb14119fc2c97d0eee5a32b46e0acff4fbf7b +++ src/model/Tags.cpp f0fab0f388865ab22baea3acf6e5d98c13835b99 @@ -81,9 +81,8 @@ void Tags::parseOutput() if (entry.sym == "revision") { - I(entry.vals.size() == 1); - tag.revision = entry.vals.at(0); - continue; + I(!entry.hash.isNull()); + tag.revision = entry.hash; } if (entry.sym == "signer") ============================================================ --- src/monotone/FileExporter.cpp 69943651783b092d180d1b8c350f1665bed145e4 +++ src/monotone/FileExporter.cpp fe0cd260a3d9c57b64a3c0c1e2de197fe77b531f @@ -71,9 +71,9 @@ bool FileExporter::exportFile(const File bool FileExporter::exportFile(const FileEntry & entry) { - if (entry.second) + if (entry.is_dir) { - if (!entry.first.isEmpty() && !rootDir.mkpath(entry.first)) + if (entry.path.isEmpty() || !rootDir.mkpath(entry.path)) { C("Cannot create directory"); return false; @@ -85,7 +85,7 @@ bool FileExporter::exportFile(const File int commandNumber; if (!mtn->executeCommand( - QStringList() << "get_file_of" << entry.first, + QStringList() << "get_file_of" << entry.path, QStringList() << "r" << revision, commandNumber) || mtn->getReturnCode(commandNumber) > 0) { @@ -93,7 +93,7 @@ bool FileExporter::exportFile(const File return false; } - QFile file(rootDir.filePath(entry.first)); + QFile file(rootDir.filePath(entry.path)); if (file.exists()) file.remove(); if (!file.open(QIODevice::WriteOnly)) ============================================================ --- src/monotone/Monotone.cpp 8d3d1bfe62b2dcb9f94f5f7f676e85bca5b16bbd +++ src/monotone/Monotone.cpp 21de72375ce610dba385952d2ba8edb939b42810 @@ -75,7 +75,6 @@ #include "Monotone.h" #include "SignalWaiter.h" #include "StdioParser.h" -#include "Guitone.h" #include #include @@ -349,26 +348,27 @@ QString Monotone::getStderr() return stripMtnPrefix(output); } -bool Monotone::executeCommand(const QStringList & command, int & commandNumber) +bool Monotone::executeCommand(const QStringList & command, int & commandNumber, bool skipEmpty) { - return executeCommand(command, QStringList(), commandNumber); + return executeCommand(command, QStringList(), commandNumber, skipEmpty); } -bool Monotone::executeCommand(const ByteArrayList & command, int & commandNumber) +bool Monotone::executeCommand(const ByteArrayList & command, int & commandNumber, bool skipEmpty) { - return executeCommand(command, ByteArrayList(), commandNumber); + return executeCommand(command, ByteArrayList(), commandNumber, skipEmpty); } -bool Monotone::executeCommand(const QStringList & command, const QStringList & options, int & commandNumber) +bool Monotone::executeCommand(const QStringList & command, const QStringList & options, int & commandNumber, bool skipEmpty) { return executeCommand( stringToByteArrayList(command), stringToByteArrayList(options), - commandNumber + commandNumber, + skipEmpty ); } -bool Monotone::executeCommand(const ByteArrayList & command, const ByteArrayList & options, int & commandNumber) +bool Monotone::executeCommand(const ByteArrayList & command, const ByteArrayList & options, int & commandNumber, bool skipEmpty) { if (doAbortRequests || process->state() != QProcess::Running) { @@ -376,8 +376,8 @@ bool Monotone::executeCommand(const Byte return false; } - commandNumber = writeStdin(command, options); - SignalWaiter waiter(this, SIGNAL(commandFinished())); + commandNumber = writeStdin(command, options, skipEmpty); + SignalWaiter waiter(this, SIGNAL(commandFinished(int))); do { @@ -388,26 +388,27 @@ bool Monotone::executeCommand(const Byte return !doAbortRequests; } -bool Monotone::triggerCommand(const QStringList & command, int & commandNumber) +bool Monotone::triggerCommand(const QStringList & command, int & commandNumber, bool skipEmpty) { - return triggerCommand(command, QStringList(), commandNumber); + return triggerCommand(command, QStringList(), commandNumber, skipEmpty); } -bool Monotone::triggerCommand(const ByteArrayList & command, int & commandNumber) +bool Monotone::triggerCommand(const ByteArrayList & command, int & commandNumber, bool skipEmpty) { - return triggerCommand(command, ByteArrayList(), commandNumber); + return triggerCommand(command, ByteArrayList(), commandNumber, skipEmpty); } -bool Monotone::triggerCommand(const QStringList & command, const QStringList & options, int & commandNumber) +bool Monotone::triggerCommand(const QStringList & command, const QStringList & options, int & commandNumber, bool skipEmpty) { return triggerCommand( stringToByteArrayList(command), stringToByteArrayList(options), - commandNumber + commandNumber, + skipEmpty ); } -bool Monotone::triggerCommand(const ByteArrayList & command, const ByteArrayList & options, int & commandNumber) +bool Monotone::triggerCommand(const ByteArrayList & command, const ByteArrayList & options, int & commandNumber, bool skipEmpty) { if (doAbortRequests || process->state() != QProcess::Running) { @@ -415,11 +416,11 @@ bool Monotone::triggerCommand(const Byte return false; } - commandNumber = writeStdin(command, options); + commandNumber = writeStdin(command, options, skipEmpty); return true; } -int Monotone::writeStdin(const ByteArrayList & command, const ByteArrayList & options) +int Monotone::writeStdin(const ByteArrayList & command, const ByteArrayList & options, bool skipEmpty) { QByteArray commandLine; QTextStream streamCmdLine(&commandLine); @@ -433,7 +434,7 @@ int Monotone::writeStdin(const ByteArray for (int i=0, c=options.size(); i 0) + { + W(QString("%1 pending commands").arg(cnt)); + APP->restoreOverrideCursor(); + } +} bool MonotoneDelegate::triggerCommand(const QStringList & cmd) { @@ -41,35 +49,40 @@ bool MonotoneDelegate::triggerCommand(co I(obj); Monotone * mtn = MTN(obj); - connect( - mtn, SIGNAL(commandFinished()), - this, SLOT(commandFinished()) - ); - - APP->setOverrideCursor(Qt::WaitCursor); - - return mtn->triggerCommand(cmd, opts, commandNumber); + int cmdNum; + if (mtn->triggerCommand(cmd, opts, cmdNum)) + { + if (commandNumbers.size() == 0) + { + APP->setOverrideCursor(Qt::WaitCursor); + + connect( + mtn, SIGNAL(commandFinished(int)), + this, SLOT(commandFinished(int)) + ); + } + + commandNumbers.insert(cmdNum); + + return true; + } + + return false; } // FIXME: if a request is aborted, commandFinished is never signalled, // do we need to take care of that here? -void MonotoneDelegate::commandFinished() +void MonotoneDelegate::commandFinished(int cmdNum) { + if (!commandNumbers.contains(cmdNum)) return; + QObject * obj = dynamic_cast(cmdModel); I(obj); Monotone * mtn = MTN(obj); - // check if this is the command which is interesting for us - if (!mtn->isCommandFinished(commandNumber)) return; - - disconnect( - mtn, SIGNAL(commandFinished()), - this, SLOT(commandFinished()) - ); - // FIXME: does any of our models ever need the raw data? - cmdModel->setAutomateData(mtn->getDecodedData(commandNumber)); - int returnCode = mtn->getReturnCode(commandNumber); + cmdModel->setAutomateData(mtn->getDecodedData(cmdNum)); + int returnCode = mtn->getReturnCode(cmdNum); if (returnCode == 0) { @@ -82,8 +95,18 @@ void MonotoneDelegate::commandFinished() C(QString("Couldn't handle error %1").arg(returnCode)); } } - - APP->restoreOverrideCursor(); + + commandNumbers.remove(cmdNum); + + if (commandNumbers.size() == 0) + { + disconnect( + mtn, SIGNAL(commandFinished(int)), + this, SLOT(commandFinished(int)) + ); + + APP->restoreOverrideCursor(); + } } QString MonotoneDelegate::getBaseWorkspaceRevision(QObject * obj) @@ -91,16 +114,24 @@ QString MonotoneDelegate::getBaseWorkspa Monotone * mtn = MTN(obj); int commandNumber; - if (!mtn->executeCommand(QStringList() << "get_base_revision_id", commandNumber) || - mtn->getReturnCode(commandNumber) > 0) + bool ret = mtn->executeCommand( + QStringList() << "get_base_revision_id", commandNumber + ); + + QString data = mtn->getDecodedData(commandNumber); + + if (!ret || mtn->getReturnCode(commandNumber) > 0) { - W("Could not execute get_base_revision_id"); - return false; + C(QString("Could not execute get_base_revision_id: %1").arg(data)); + return QString(); } - QString baseRevision = mtn->getDecodedData(commandNumber); - baseRevision.chop(1); - return baseRevision; + data.chop(1); + + // we cannot handle workspaces with multiple parents yet + I(data.indexOf('\n') == -1); + + return data; } QString MonotoneDelegate::getOption(QObject * obj, const QString & opt) @@ -250,8 +281,15 @@ FileEntryList MonotoneDelegate::getRevis { Monotone * mtn = MTN(obj); + QStringList cmd; + cmd << "get_manifest_of"; + if (!revision.isEmpty()) + { + cmd << revision; + } + int cmdNum; - mtn->executeCommand(QStringList() << "get_manifest_of" << revision, cmdNum); + mtn->executeCommand(cmd, cmdNum); QString data = mtn->getDecodedData(cmdNum); FileEntryList entries; @@ -272,18 +310,40 @@ FileEntryList MonotoneDelegate::getRevis StanzaList stanzas = parser.getStanzas(); foreach (Stanza st, stanzas) { + FileEntry entry; + bool is_item = false; + foreach (StanzaEntry en, st) { - if (en.sym == "format_version") break; - - FileEntry entry; - entry.first = en.vals.at(0); - I(en.sym == "dir" || en.sym == "file"); - entry.second = en.sym == "dir"; - - entries.append(entry); - break; + if (en.sym == "format_version") + { + I(en.vals.size() == 1 && en.vals.at(0) == "1"); + break; + } + else + if (en.sym == "dir" || en.sym == "file") + { + is_item = true; + entry.path = en.vals.at(0); + entry.is_dir = en.sym == "dir"; + } + else + if (en.sym == "content") + { + I(is_item); + entry.fileid = en.hash; + } + else + if (en.sym == "attr") + { + I(is_item); + I(en.vals.size() == 2); + entry.attrs.insert(en.vals.at(0), en.vals.at(1)); + } + else + I(false); } + if (is_item) entries.append(entry); } return entries; } @@ -333,3 +393,23 @@ QStringList MonotoneDelegate::getPrivate } return keys; } + +QString MonotoneDelegate::getFileId(QObject * obj, const QString & path) +{ + Monotone * mtn = MTN(obj); + + int cmdNum; + mtn->executeCommand(QStringList() << "identify" << path, cmdNum); + + QString data = mtn->getDecodedData(cmdNum); + + if (mtn->getReturnCode(cmdNum) > 0) + { + C(QString("Couldn't identify path: %1").arg(data)); + return QString(); + } + + data.chop(1); + return data; +} + ============================================================ --- src/monotone/MonotoneDelegate.h 907a718c030f96ed3c2951e5b5b1442b72b6891a +++ src/monotone/MonotoneDelegate.h 6971e5c0d79d50fc766f84fb395cf648da80c8e5 @@ -25,6 +25,7 @@ #include "vocab.h" #include +#include class MonotoneDelegate : public QObject @@ -44,15 +45,16 @@ public: static QStringList resolveSelector(QObject *, const QString &); static RevisionCerts getRevisionCerts(QObject *, const QString &); static QString getDatabaseFilePath(QObject *); - static FileEntryList getRevisionManifest(QObject *, const QString &); + static FileEntryList getRevisionManifest(QObject *, const QString & rev = QString()); static QStringList getPrivateKeyList(QObject *); + static QString getFileId(QObject *, const QString &); private: AutomateCommand * cmdModel; - int commandNumber; + QSet commandNumbers; private slots: - void commandFinished(); + void commandFinished(int); }; #endif ============================================================ --- src/monotone/WorkspaceCommitter.cpp 976aad817f2bb63cbb6c0220e0f30cf6bd8f6517 +++ src/monotone/WorkspaceCommitter.cpp 2c27ad30e62fd2782321ae897406b9f095ec5c53 @@ -22,6 +22,7 @@ #include "MonotoneDelegate.h" #include "Guitone.h" #include "BasicIOParser.h" +#include "BasicIOWriter.h" #include #include @@ -66,8 +67,8 @@ bool WorkspaceCommitter::run() QString fullRevision = data; - BasicIOParser parser(fullRevision); - if (!parser.parse()) + BasicIOParser revparser(fullRevision); + if (!revparser.parse()) { C("Could not parse basic_io."); return false; @@ -75,7 +76,7 @@ bool WorkspaceCommitter::run() QList > fileChanges; - StanzaList stanzas = parser.getStanzas(); + StanzaList stanzas = revparser.getStanzas(); foreach (Stanza st, stanzas) { QPair pair; @@ -100,7 +101,8 @@ bool WorkspaceCommitter::run() if (isPatch && en.sym == "from") { I(!pair.first.isEmpty()); - pair.second = en.vals.at(0); + I(!en.hash.isNull()); + pair.second = en.hash; fileChanges.append(pair); break; } @@ -131,6 +133,7 @@ bool WorkspaceCommitter::run() data.chop(1); revisionId = data; + I(revisionId.length() == 40); // attach certificates QMapIterator i(certs); @@ -155,9 +158,78 @@ bool WorkspaceCommitter::run() } D(QString("Committed revision %1").arg(revisionId)); + + // update _MTN/revision and _MTN/options + I(workspaceDir.cd("_MTN")); + + return writeRevision() && writeOptions(); +} + +bool WorkspaceCommitter::writeRevision() +{ + QFile file(workspaceDir.filePath("revision")); + if (!file.open(QIODevice::WriteOnly)) + { + C("Can't open revision for writing"); + return false; + } + + StanzaList stanzas; + + Stanza format; + format.append(StanzaEntry("format_version", QStringList() << "1")); + stanzas.append(format); + + Stanza manifest; + manifest.append(StanzaEntry("new_manifest", + "0000000000000000000000000000000000000000")); + stanzas.append(manifest); + + Stanza oldrev; + oldrev.append(StanzaEntry("old_revision", revisionId)); + stanzas.append(oldrev); + + BasicIOWriter writer(stanzas); + QString rev = writer.write(); + I(file.write(rev.toLatin1())); + file.close(); + return true; } +bool WorkspaceCommitter::writeOptions() +{ + QFile file(workspaceDir.filePath("options")); + if (!file.open(QIODevice::ReadWrite)) + { + C("Can't open options for reading and writing"); + return false; + } + + // read and parse the basic_io stanzas and set the correct branch name + BasicIOParser parser(QString::fromUtf8(file.readAll())); + I(parser.parse()); + StanzaList stanzas = parser.getStanzas(); + I(stanzas.size() == 1); + + for (int i=0, j=stanzas[0].size(); i #include +#include -class AbstractParser : public QObject +class AbstractParser { - Q_OBJECT public: AbstractParser(const QByteArray &); AbstractParser(const QString &); - ~AbstractParser(); + virtual ~AbstractParser(); virtual bool parse() = 0; inline QByteArray getLeftBytes() const { return input; } ============================================================ --- src/util/BasicIOParser.cpp 41c485e6ae2759a0084019bf94663f20b0730949 +++ src/util/BasicIOParser.cpp f11c59b9074eee097cb97feea7f93bdd146ccb0d @@ -51,12 +51,11 @@ Stanza BasicIOParser::getStanza() W("Couldn't get symbol."); } QString hash(getHash()); + // was this a hash? if (!hash.isNull()) { - // for now we do not make a distinction between normal hashes - // and content value lists - entry.vals.append(hash); + entry.hash = hash; } else { ============================================================ --- src/util/BasicIOParser.h 2abdc40c948b2678ceeebf976e5424076036a83d +++ src/util/BasicIOParser.h b96df0f29dd64ac740c6e8a7f34c080406d3837e @@ -22,20 +22,10 @@ #define BASICIO_PARSER_H #include "AbstractParser.h" +#include "vocab.h" -#include - -typedef struct { - QString sym; - QStringList vals; -} StanzaEntry; - -typedef QList Stanza; -typedef QList StanzaList; - class BasicIOParser : public AbstractParser { - Q_OBJECT public: BasicIOParser(const QString &); ============================================================ --- src/util/CocoaUtil.h 8424a0c7f877f0167271bb2386fd00472a32f551 +++ src/util/CocoaUtil.h 7bab17bbe14218ea6e1dc33bbca110c0cc13a248 @@ -5,7 +5,6 @@ #ifndef COCOAUTIL_H #define COCOAUTIL_H -#include #include class SUUpdater; @@ -14,7 +13,6 @@ namespace CocoaUtil { void initialize(); void checkForUpdates(); - QString FSRefToPath(FSRef ref); }; #endif ============================================================ --- src/util/CocoaUtil.mm af7058410a39e0fa1f7fdb9d1ff5019261fd0960 +++ src/util/CocoaUtil.mm 5fe5b302f4e7198a04700856bab989b3b7e2f44c @@ -20,15 +20,3 @@ void CocoaUtil::checkForUpdates() [updater checkForUpdates:nil]; } -QString CocoaUtil::FSRefToPath(FSRef fsref) -{ - CFURLRef url = CFURLCreateFromFSRef(kCFAllocatorDefault, &fsref); - if (!url) - { - return QString(); - } - NSString * pathName = (NSString*)CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); - [pathName autorelease]; - CFRelease(url); - return QString::fromUtf8([pathName UTF8String]); -} ============================================================ --- src/util/DebugLog.cpp ebbb4ff026ff490147e9c81f53f3c1b23c82febf +++ src/util/DebugLog.cpp 513339e2eb09994edeed4ad8ed3d98b9a6101c81 @@ -56,12 +56,12 @@ DebugLog::DebugLog() QString sep; QDate today = QDate::currentDate(); sep.fill('=', 40); - log(Debug, sep); - log(Debug, + log(Info, sep); + log(Info, QString(" guitone session started (%1)") .arg(today.toString("yyyy-MM-dd")) ); - log(Debug, sep); + log(Info, sep); } DebugLog::~DebugLog() @@ -89,13 +89,15 @@ void DebugLog::log(Type t, QString msg) if (logLevel == Fatal && t > Fatal) return; if (logLevel == Critical && t > Critical) return; if (logLevel == Warn && t > Warn) return; + if (logLevel == Info && t > Info) return; if (logLevel == Debug && t > Debug) return; QMap errors; errors[0] = "fatal"; errors[1] = "critical"; errors[2] = "warning"; - errors[3] = "debug"; + errors[3] = "info"; + errors[4] = "debug"; // do not use I here since this calls qFatal and results in // an endless loop @@ -131,11 +133,20 @@ void DebugLog::closeLogfile() if (logFile.isOpen()) logFile.close(); } +#ifndef QT_NO_DEBUG void DebugLog::debug(QString msg) { singleton()->log(Debug, msg); } +#else +void DebugLog::debug(QString msg) { Q_UNUSED(msg); } +#endif +void DebugLog::info(QString msg) +{ + singleton()->log(Info, msg); +} + void DebugLog::warn(QString msg) { singleton()->log(Warn, msg); ============================================================ --- src/util/DebugLog.h 15ae25ff50330f9a76933a81f344875cd72554d8 +++ src/util/DebugLog.h 94f2cac41d2381a8ab113dc5adb29131844beaf4 @@ -30,7 +30,7 @@ public: // higher levels include lower ones, i.e. // 'warn' also prints out 'critical' and 'fatal'; // 'debug' prints out everything we have - enum Level {Fatal = 1, Critical, Warn, Debug }; + enum Level {Fatal = 1, Critical, Warn, Info, Debug }; // as we have different Levels each level's name is also // the name for a specific log type typedef Level Type; @@ -45,6 +45,7 @@ public: static QString logFilePath(); + static void info(QString); static void debug(QString); inline static void debug(const char * msg) { return debug(QString::fromUtf8(msg)); } static void warn(QString); ============================================================ --- src/util/DiffParser.cpp e98a52a2c7bee64b13376f3cf71b47629a213a7d +++ src/util/DiffParser.cpp 4a0256295820e64a621bee2c82c34357ac711b91 @@ -37,7 +37,11 @@ void DiffParser::parse(const QString & i void DiffParser::parse(const QString & input) { - if (input.length() == 0) return; + if (input.length() == 0) + { + W("Nothing to parse"); + return; + } // remove last newline character QString in(input); @@ -139,9 +143,9 @@ void DiffParser::parse(const QString & i } } -Diff* DiffParser::getDiff(const QString & filename) +Diff * DiffParser::getDiff(const QString & filename) { - I(fileDiffs.contains(filename)); + if (!fileDiffs.contains(filename)) return 0; return fileDiffs.value(filename); } ============================================================ --- src/util/StdioParser.h 8488d8ccabd54eef76d773c0ccaf91ac47d95d3d +++ src/util/StdioParser.h ed0742f484843b709c946a2c030cca934432335d @@ -25,7 +25,6 @@ class StdioParser : public AbstractParse class StdioParser : public AbstractParser { - Q_OBJECT public: StdioParser(const QByteArray &); ============================================================ --- src/view/AttributesView.cpp 622bd8e562abeaa8299ecac4d3a725a0f8eb73ae +++ src/view/AttributesView.cpp e4b0b75ba4c9abd6fa8efa2c6def8a4f2ae6dc96 @@ -18,14 +18,195 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ +#include "vocab.h" #include "AttributesView.h" +#include "AddEditAttribute.h" +#include "Guitone.h" -AttributesView::AttributesView(QWidget* parent) -: TreeView(parent) +#include + +// FIXME: this dependency is ugly, we should probably use +// Qt's row editing, but this is ugly (and complex) +// to implement as well +#include "GetAttributes.h" + +#include + +AttributesView::AttributesView(QWidget * parent) : TreeView(parent) { setRootIsDecorated(false); setItemsExpandable(false); + + QHeaderView * headerView = header(); + headerView->setClickable(true); + headerView->setResizeMode(QHeaderView::ResizeToContents); + + connect( + headerView, SIGNAL(sectionClicked(int)), + this, SLOT(sectionClicked(int)) + ); + + connect( + this, SIGNAL(contextMenuRequested(const QModelIndexList &, const QPoint &)), + this, SLOT(menuRequested(const QModelIndexList &, const QPoint &)) + ); + + connect( + this, SIGNAL(doubleClicked(const QModelIndex &)), + this, SLOT(aboutToEditAttribute(const QModelIndex &)) + ); } AttributesView::~AttributesView() {} +void AttributesView::sectionClicked(int logicalIndex) +{ + // do not popup the menu if we haven't focused a valid item + GetAttributes * attrModel = qobject_cast(model()); + if (attrModel->currentPath().isEmpty()) return; + + if (logicalIndex != 0) return; + + QHeaderView * headerView = header(); + int visualIndex = headerView->visualIndex(logicalIndex); + + int left = 0; + int top = headerView->height(); + + if (visualIndex > 0) + { + for (int i=0; isectionSize(headerView->logicalIndex(i)); + } + } + + if (left > headerView->width()) + { + left = headerView->width() - headerView->sectionSize(logicalIndex); + } + + QMenu popupMenu(this); + popupMenu.addAction( + tr("add new attribute"), this, SLOT(addAttribute()) + ); + popupMenu.exec(mapToGlobal(QPoint(left, top))); +} + +void AttributesView::menuRequested(const QModelIndexList & indexes, const QPoint & pt) +{ + if (indexes.size() != 1) return; + + selectedIndex = indexes.at(0); + + QMenu popupMenu(this); + + QFont activeFont; + activeFont.setBold(true); + + popupMenu.addAction( + tr("edit attribute"), this, SLOT(editAttribute()) + )->setFont(activeFont); + + popupMenu.addAction( + tr("drop attribute"), this, SLOT(dropAttribute()) + ); + + popupMenu.exec(pt); +} + +void AttributesView::addAttribute() +{ + AddEditAttribute dlg(this); + + if (dlg.exec() == QDialog::Accepted) + { + GetAttributes * attrModel = qobject_cast(model()); + + QStringList cmd; + cmd << "set_attribute"; + cmd << attrModel->currentPath(); + cmd << dlg.getKey() << dlg.getValue(); + + Monotone * mtn = MTN(this); + int cmdNum = 0; + // do not skip empty parameters + mtn->executeCommand(cmd, cmdNum, false); + + if (mtn->getReturnCode(cmdNum) > 0) + { + C(QString("Couldn't set attribute")); + return; + } + + attrModel->readAttributes(attrModel->currentPath()); + } +} + +void AttributesView::aboutToEditAttribute(const QModelIndex & index) +{ + if (!index.isValid()) return; + selectedIndex = index; + editAttribute(); +} + +void AttributesView::editAttribute() +{ + QModelIndex keyIdx = model()->index(selectedIndex.row(), 1, QModelIndex()); + QModelIndex valIdx = model()->index(selectedIndex.row(), 2, QModelIndex()); + + // UserRole is overloaded with the actual string value of the + // key / value pair + QString key = model()->data(keyIdx, Qt::UserRole).toString(); + QString val = model()->data(valIdx, Qt::UserRole).toString(); + + AddEditAttribute dlg(this, key, val); + + if (dlg.exec() == QDialog::Accepted) + { + GetAttributes * attrModel = qobject_cast(model()); + + QStringList cmd; + cmd << "set_attribute"; + cmd << attrModel->currentPath(); + cmd << dlg.getKey() << dlg.getValue(); + + Monotone * mtn = MTN(this); + int cmdNum = 0; + // do not skip empty parameters + mtn->executeCommand(cmd, cmdNum, false); + + if (mtn->getReturnCode(cmdNum) > 0) + { + C(QString("Couldn't set attribute")); + return; + } + + attrModel->readAttributes(attrModel->currentPath()); + } +} + +void AttributesView::dropAttribute() +{ + GetAttributes * attrModel = qobject_cast(model()); + QModelIndex keyIdx = attrModel->index(selectedIndex.row(), 1, QModelIndex()); + + QStringList cmd; + cmd << "drop_attribute"; + cmd << attrModel->currentPath(); + cmd << model()->data(keyIdx, Qt::UserRole).toString(); // the key + + Monotone * mtn = MTN(this); + int cmdNum = 0; + // do not skip empty parameters + mtn->executeCommand(cmd, cmdNum, false); + + if (mtn->getReturnCode(cmdNum) > 0) + { + C(QString("Couldn't drop attribute")); + return; + } + + attrModel->readAttributes(attrModel->currentPath()); +} + ============================================================ --- src/view/AttributesView.h 6331d48f84cd19a9389ea33efd8e11278867b3eb +++ src/view/AttributesView.h 919369059dde5efb8b1e8b0c0d1e25e49dfc6825 @@ -22,6 +22,7 @@ #define ATTRIBUTES_VIEW_H #include "TreeView.h" +#include class AttributesView : public TreeView { @@ -30,6 +31,19 @@ public: public: AttributesView(QWidget*); ~AttributesView(); + +private slots: + void sectionClicked(int); + void menuRequested(const QModelIndexList &, const QPoint &); + + void addAttribute(); + void editAttribute(); + void aboutToEditAttribute(const QModelIndex &); + void dropAttribute(); + +private: + QModelIndex selectedIndex; + QString path; }; #endif ============================================================ --- src/view/InventoryView.cpp bb051af5d94c6e995a1b33658b5d529b87b2df95 +++ src/view/InventoryView.cpp 441ff9c1a21693b33b00efe2894d0f50425f6b3c @@ -24,6 +24,7 @@ #include "InventoryItem.h" #include "Monotone.h" #include "FileDiff.h" +#include "FileHistory.h" #include "RevisionDiff.h" #include "vocab.h" #include "Guitone.h" @@ -88,17 +89,93 @@ void InventoryView::setType(Type t) } } +void InventoryView::createAndConnectContextActions() +{ + actChdir = new QAction(tr("Go into"), this); + actChdir->setStatusTip(tr("Go into the directory")); + connect(actChdir, SIGNAL(triggered()), this, SLOT(slotChdir())); + + actOpen = new QAction(tr("Open"), this); + actOpen->setStatusTip(tr("Open in default program")); + connect(actOpen, SIGNAL(triggered()), this, SLOT(slotOpen())); + + actAdd = new QAction(tr("Add"), this); + actAdd->setStatusTip(tr("Add to workspace")); + connect(actAdd, SIGNAL(triggered()), this, SLOT(slotAdd())); + + actRemove = new QAction(tr("Remove"), this); + actRemove->setStatusTip(tr("Remove from workspace")); + connect(actRemove, SIGNAL(triggered()), this, SLOT(slotRemove())); + + actCommit = new QAction(tr("Commit"), this); + actCommit->setStatusTip(tr("Commit")); + connect(actCommit, SIGNAL(triggered()), this, SLOT(slotCommit())); + + actIgnore = new QAction(tr("Ignore"), this); + actIgnore->setStatusTip(tr("Ignore file")); + connect(actIgnore, SIGNAL(triggered()), this, SLOT(slotIgnore())); + + actUnignore = new QAction(tr("Unignore"), this); + actUnignore->setStatusTip(tr("Unignore file")); + connect(actUnignore, SIGNAL(triggered()), this, SLOT(slotUnignore())); + + actRevert = new QAction(tr("Revert"), this); + actRevert->setStatusTip(tr("Revert uncommitted changes")); + connect(actRevert, SIGNAL(triggered()), this, SLOT(slotRevert())); + + actFileDiff = new QAction(tr("Diff"), this); + actFileDiff->setStatusTip(tr("Diff against base revision")); + connect(actFileDiff, SIGNAL(triggered()), this, SLOT(slotFileDiff())); + + actFileHistory = new QAction(tr("History"), this); + actFileHistory->setStatusTip(tr("Display the history of this file")); + connect(actFileHistory, SIGNAL(triggered()), this, SLOT(slotFileHistory())); + + actRevisionDiff = new QAction(tr("Diff all"), this); + actRevisionDiff->setStatusTip(tr("Show all differences")); + connect(actRevisionDiff, SIGNAL(triggered()), this, SLOT(slotRevisionDiff())); + + actRename = new QAction(tr("Rename"), this); + actRename->setStatusTip(tr("Rename file")); + connect(actRename, SIGNAL(triggered()), this, SLOT(slotRename())); + + actAddMultiple = new QAction(tr("Add %1 items"), this); + actAddMultiple->setStatusTip(tr("Add multiple items")); + connect(actAddMultiple, SIGNAL(triggered()), this, SLOT(slotAdd())); + + actRemoveMultiple = new QAction(tr("Remove %1 items"), this); + actRemoveMultiple->setStatusTip(tr("Remove multiple items")); + connect(actRemoveMultiple, SIGNAL(triggered()), this, SLOT(slotRemove())); + + actCommitMultiple = new QAction(tr("Commit %1 items"), this); + actCommitMultiple->setStatusTip(tr("Commit multiple items")); + connect(actCommitMultiple, SIGNAL(triggered()), this, SLOT(slotCommit())); + + actIgnoreMultiple = new QAction(tr("Ignore %1 items"), this); + actIgnoreMultiple->setStatusTip(tr("Ignore multiple items")); + connect(actIgnoreMultiple, SIGNAL(triggered()), this, SLOT(slotIgnore())); + + actUnignoreMultiple = new QAction(tr("Unignore %1 items"), this); + actUnignoreMultiple->setStatusTip(tr("Unignore multiple items")); + connect(actUnignoreMultiple, SIGNAL(triggered()), this, SLOT(slotUnignore())); + + actRevertMultiple = new QAction(tr("Revert %1 items"), this); + actRevertMultiple->setStatusTip(tr("Revert multiple items")); + connect(actRevertMultiple, SIGNAL(triggered()), this, SLOT(slotRevert())); +} + InventoryView::~InventoryView() { delete actChdir; - delete actOpen; - delete actAdd; - delete actRemove; - delete actCommit; - delete actIgnore; - delete actUnignore; + delete actOpen; + delete actAdd; + delete actRemove; + delete actCommit; + delete actIgnore; + delete actUnignore; delete actRevert; - delete actFileDiff; + delete actFileDiff; + delete actFileHistory; delete actRevisionDiff; delete actRename; delete actAddMultiple; @@ -217,6 +294,12 @@ void InventoryView::slotContextMenuReque menu.addAction(actFileDiff); } } + + if (item->hasNotStatus(InventoryItem::Added) && + !item->isDirectory()) + { + menu.addAction(actFileHistory); + } } // @@ -332,77 +415,6 @@ void InventoryView::slotContextMenuReque } } -void InventoryView::createAndConnectContextActions(void) -{ - actChdir = new QAction(tr("Go into"), this); - actChdir->setStatusTip(tr("Go into the directory")); - connect(actChdir, SIGNAL(triggered()), this, SLOT(slotChdir())); - - actOpen = new QAction(tr("Open"), this); - actOpen->setStatusTip(tr("Open in default program")); - connect(actOpen, SIGNAL(triggered()), this, SLOT(slotOpen())); - - actAdd = new QAction(tr("Add"), this); - actAdd->setStatusTip(tr("Add to workspace")); - connect(actAdd, SIGNAL(triggered()), this, SLOT(slotAdd())); - - actRemove = new QAction(tr("Remove"), this); - actRemove->setStatusTip(tr("Remove from workspace")); - connect(actRemove, SIGNAL(triggered()), this, SLOT(slotRemove())); - - actCommit = new QAction(tr("Commit"), this); - actCommit->setStatusTip(tr("Commit")); - connect(actCommit, SIGNAL(triggered()), this, SLOT(slotCommit())); - - actIgnore = new QAction(tr("Ignore"), this); - actIgnore->setStatusTip(tr("Ignore file")); - connect(actIgnore, SIGNAL(triggered()), this, SLOT(slotIgnore())); - - actUnignore = new QAction(tr("Unignore"), this); - actUnignore->setStatusTip(tr("Unignore file")); - connect(actUnignore, SIGNAL(triggered()), this, SLOT(slotUnignore())); - - actRevert = new QAction(tr("Revert"), this); - actRevert->setStatusTip(tr("Revert uncommitted changes")); - connect(actRevert, SIGNAL(triggered()), this, SLOT(slotRevert())); - - actFileDiff = new QAction(tr("Diff"), this); - actFileDiff->setStatusTip(tr("Diff against base revision")); - connect(actFileDiff, SIGNAL(triggered()), this, SLOT(slotFileDiff())); - - actRevisionDiff = new QAction(tr("Diff all"), this); - actRevisionDiff->setStatusTip(tr("Show all differences")); - connect(actRevisionDiff, SIGNAL(triggered()), this, SLOT(slotRevisionDiff())); - - actRename = new QAction(tr("Rename"), this); - actRename->setStatusTip(tr("Rename file")); - connect(actRename, SIGNAL(triggered()), this, SLOT(slotRename())); - - actAddMultiple = new QAction(tr("Add %1 items"), this); - actAddMultiple->setStatusTip(tr("Add multiple items")); - connect(actAddMultiple, SIGNAL(triggered()), this, SLOT(slotAdd())); - - actRemoveMultiple = new QAction(tr("Remove %1 items"), this); - actRemoveMultiple->setStatusTip(tr("Remove multiple items")); - connect(actRemoveMultiple, SIGNAL(triggered()), this, SLOT(slotRemove())); - - actCommitMultiple = new QAction(tr("Commit %1 items"), this); - actCommitMultiple->setStatusTip(tr("Commit multiple items")); - connect(actCommitMultiple, SIGNAL(triggered()), this, SLOT(slotCommit())); - - actIgnoreMultiple = new QAction(tr("Ignore %1 items"), this); - actIgnoreMultiple->setStatusTip(tr("Ignore multiple items")); - connect(actIgnoreMultiple, SIGNAL(triggered()), this, SLOT(slotIgnore())); - - actUnignoreMultiple = new QAction(tr("Unignore %1 items"), this); - actUnignoreMultiple->setStatusTip(tr("Unignore multiple items")); - connect(actUnignoreMultiple, SIGNAL(triggered()), this, SLOT(slotUnignore())); - - actRevertMultiple = new QAction(tr("Revert %1 items"), this); - actRevertMultiple->setStatusTip(tr("Revert multiple items")); - connect(actRevertMultiple, SIGNAL(triggered()), this, SLOT(slotRevert())); -} - void InventoryView::slotChdir() { QItemSelectionModel *selectionModel = this->selectionModel(); @@ -448,7 +460,7 @@ void InventoryView::changeDirectory(cons ); } -void InventoryView::slotOpen(void) +void InventoryView::slotOpen() { QModelIndex index(getSingleSelection()); if (!index.isValid()) return; @@ -480,27 +492,27 @@ void InventoryView::slotOpen(void) } } -void InventoryView::slotAdd(void) +void InventoryView::slotAdd() { C("Not implemented."); } -void InventoryView::slotRemove(void) +void InventoryView::slotRemove() { C("Not implemented."); } -void InventoryView::slotCommit(void) +void InventoryView::slotCommit() { C("Not implemented."); } -void InventoryView::slotRevert(void) +void InventoryView::slotRevert() { C("Not implemented."); } -void InventoryView::slotRename(void) +void InventoryView::slotRename() { C("Not implemented."); } @@ -511,17 +523,17 @@ void InventoryView::slotRename(void) // Q: If this file is under version control, should a change // be committed in the background? // -void InventoryView::slotIgnore(void) +void InventoryView::slotIgnore() { C("Not implemented."); } -void InventoryView::slotUnignore(void) +void InventoryView::slotUnignore() { C("Not implemented."); } -void InventoryView::slotFileDiff(void) +void InventoryView::slotFileDiff() { QModelIndex index(getSingleSelection()); if (!index.isValid()) return; @@ -540,13 +552,41 @@ void InventoryView::slotFileDiff(void) clearSelection(); } -void InventoryView::slotRevisionDiff(void) +void InventoryView::slotFileHistory() { QModelIndex index(getSingleSelection()); if (!index.isValid()) return; InventoryItem * item = static_cast(index.internalPointer()); + QString path = item->getPath(); + // determine the original path, if needed + if (item->hasStatus(InventoryItem::RenamedFrom)) + { + InventoryItem * newItem = item->getRenamedTo(); + I(newItem); + path = newItem->getPath(); + } + if (item->hasStatus(InventoryItem::RenamedTo)) + { + InventoryItem * oldItem = item->getRenamedFrom(); + I(oldItem); + path = oldItem->getPath(); + } + + FileHistory dlg(this, path); + dlg.exec(); + + clearSelection(); +} + +void InventoryView::slotRevisionDiff() +{ + QModelIndex index(getSingleSelection()); + if (!index.isValid()) return; + + InventoryItem * item = static_cast(index.internalPointer()); + RevisionDiff dlg(this); dlg.init(item->getPath(), QString(), QString()); dlg.exec(); ============================================================ --- src/view/InventoryView.h 0ad2ef23cc24538fcd3fd656b712f7b6dca17e3e +++ src/view/InventoryView.h fd0d9c5eb87a628057d22b2a41426dcbbe84ca03 @@ -31,15 +31,15 @@ class InventoryView : public TreeView class InventoryView : public TreeView { - Q_OBJECT + Q_OBJECT public: - enum Type { FolderTree, FileList }; - - InventoryView(QWidget*); - ~InventoryView(); + enum Type { FolderTree, FileList }; + + InventoryView(QWidget*); + ~InventoryView(); - void setModel(QSortFilterProxyModel *); + void setModel(QSortFilterProxyModel *); void setType(Type); signals: @@ -47,20 +47,21 @@ private: private: void setModel(QAbstractItemModel *); - void createAndConnectContextActions(void); - void closeEvent(void); + void createAndConnectContextActions(); + void closeEvent(); QModelIndex getSingleSelection(bool mapToSource = true); QAction * actChdir; QAction * actOpen; - QAction * actAdd; - QAction * actRemove; - QAction * actCommit; - QAction * actIgnore; + QAction * actAdd; + QAction * actRemove; + QAction * actCommit; + QAction * actIgnore; QAction * actUnignore; QAction * actRevert; QAction * actRename; QAction * actFileDiff; + QAction * actFileHistory; QAction * actRevisionDiff; QAction * actAddMultiple; @@ -82,17 +83,18 @@ private slots: void itemClicked(const QModelIndex & index); void slotContextMenuRequested(const QModelIndexList &, const QPoint &); - void slotChdir(void); - void slotOpen(void); - void slotAdd(void); - void slotRemove(void); - void slotCommit(void); - void slotIgnore(void); - void slotUnignore(void); - void slotRevert(void); - void slotRename(void); - void slotFileDiff(void); - void slotRevisionDiff(void); + void slotChdir(); + void slotOpen(); + void slotAdd(); + void slotRemove(); + void slotCommit(); + void slotIgnore(); + void slotUnignore(); + void slotRevert(); + void slotRename(); + void slotFileDiff(); + void slotFileHistory(); + void slotRevisionDiff(); }; #endif ============================================================ --- src/view/MainWindow.cpp c485c30152a7f43a73d96c33e13054771c5f0250 +++ src/view/MainWindow.cpp c4b6cf1f128df54b18ba2a5b8ca2a2fddc0231aa @@ -22,7 +22,7 @@ #include "Monotone.h" #include "Inventory.h" #include "InventoryItem.h" -#include "Attributes.h" +#include "GetAttributes.h" #include "InventoryProxyModel.h" #include "Splitter.h" #include "InventoryView.h" @@ -30,7 +30,6 @@ #include "UpdateWorkspace.h" #include "CommitRevision.h" #include "CheckoutRevision.h" -#include "ApplicationUpdate.h" #include "Platform.h" #include "Preferences.h" #include "KeyManagement.h" @@ -38,10 +37,13 @@ #include "Settings.h" #include "ChangesetBrowser.h" #include "WorkspaceCreator.h" +#include "UnaccountedRenames.h" #include "Guitone.h" #ifdef Q_WS_MAC #include "CocoaUtil.h" +#else +#include "ApplicationUpdate.h" #endif #include @@ -55,8 +57,6 @@ MainWindow::MainWindow() MainWindow::MainWindow() : QMainWindow(), closeCounter(0) { - setAttribute(Qt::WA_MacMetalStyle); - // ensure that the shortcut keys are properly recognized by linguist QShortcut::tr("Ctrl"); QShortcut::tr("Alt"); @@ -67,7 +67,7 @@ MainWindow::MainWindow() // create the main models invModel = new Inventory(this); - attrModel = new Attributes(this); + attrModel = new GetAttributes(this); connect( invModel, SIGNAL(invalidWorkspaceFormat(const QString &)), @@ -90,11 +90,11 @@ MainWindow::MainWindow() // query attributes on click connect( treeView, SIGNAL(clicked(const QModelIndex &)), - attrModel, SLOT(readAttributes(const QModelIndex &)) + this, SLOT(readAttributes(const QModelIndex &)) ); connect( listView, SIGNAL(clicked(const QModelIndex &)), - attrModel, SLOT(readAttributes(const QModelIndex &)) + this, SLOT(readAttributes(const QModelIndex &)) ); // filelist/tree synchronization @@ -192,12 +192,7 @@ bool MainWindow::doLoadWorkspace(QString if (!invModel->readInventory()) { - QMessageBox::information( - this, - tr("Unable to execute command"), - tr("Unable to execute '%1' - maybe another command is still running?").arg("inventory"), - QMessageBox::Ok - ); + C("Could not read inventory"); return false; } @@ -210,24 +205,6 @@ bool MainWindow::doLoadWorkspace(QString return true; } -void MainWindow::closeEvent(QCloseEvent * event) -{ - I(closeCounter >= 0); - - // ignore the close event if there are still open dialog windows - if (closeCounter > 0) - { - D(QString("Ignoring close request for %1").arg((int)this)); - event->ignore(); - return; - } - - // the last closed window sets the geometry for the next one which is opened - Settings::setWindowGeometry(saveGeometry(), "MainWindow_mode" + mode); - event->accept(); - emit windowClosed(this); -} - void MainWindow::on_actionOpen_Database_triggered() { QString fn = QFileDialog::getOpenFileName( @@ -556,7 +533,10 @@ void MainWindow::on_actionCommit_revisio void MainWindow::on_actionCommit_revision_triggered() { CommitRevision dialog(this); - dialog.execDocumentModal(); + if (dialog.execDocumentModal() == QDialog::Accepted) + { + on_actionReload_workspace_triggered(); + } } void MainWindow::on_actionCheckout_revision_triggered() @@ -658,3 +638,67 @@ void MainWindow::disableClosing() closeCounter++; } +void MainWindow::closeEvent(QCloseEvent * event) +{ + I(closeCounter >= 0); + + // ignore the close event if there are still open dialog windows + if (closeCounter > 0) + { + D(QString("Ignoring close request for %1").arg((int)this)); + event->ignore(); + return; + } + + // the last closed window sets the geometry for the next one which is opened + Settings::setWindowGeometry(saveGeometry(), "MainWindow_mode" + mode); + event->accept(); + emit windowClosed(this); +} + +void MainWindow::on_actionReload_workspace_triggered() +{ + bool ret = invModel->readInventory(); + if (!ret) C("Could not read inventory."); +} + +void MainWindow::on_actionFind_unaccounted_renames_triggered() +{ + QMap renames = invModel->findUnaccountedRenames(); + if (renames.size() == 0) + { + QMessageBox::information( + this, + tr("Nothing found"), + tr("No unaccounted renames found for this workspace."), + QMessageBox::Ok + ); + return; + } + + UnaccountedRenames dlg(this, renames); + dlg.execDocumentModal(); +} + +void MainWindow::readAttributes(const QModelIndex & index) +{ + QModelIndex sourceIndex = static_cast(index.model())->mapToSource(index); + InventoryItem * item = static_cast(sourceIndex.internalPointer()); + + if (item->isRootDirectory() || item->isCdUp()) + { + D("item is pseudo item (root or cdup)"); + attrModel->revert(); + return; + } + + if (item->hasStatus(InventoryItem::Dropped) || !item->isTracked()) + { + D("item is not tracked or dropped"); + attrModel->revert(); + return; + } + + attrModel->readAttributes(item->getPath()); +} + ============================================================ --- src/view/MainWindow.h 7ea22986b3ba2005db4b604c592a91365ba78122 +++ src/view/MainWindow.h 49a87144c3f5ef2d99f1ca0acc4acb11e21a402c @@ -26,7 +26,7 @@ class Inventory; class QModelIndex; class Inventory; -class Attributes; +class GetAttributes; class InventoryProxyModel; class InventoryView; class AttributesView; @@ -55,8 +55,8 @@ signals: void quitApplication(); void updatePreviousWorkspacesMenu(); void updatePreviousDatabasesMenu(); - void loadDatabase(const QString &); - void loadWorkspace(const QString &); + void loadDatabase(const QString &); + void loadWorkspace(const QString &); private slots: void on_actionOpen_Workspace_triggered(); @@ -74,21 +74,24 @@ private slots: void on_actionChangeset_browser_triggered(); void on_actionBring_all_to_front_triggered(); void on_actionCheck_for_updates_triggered(); + void on_actionReload_workspace_triggered(); + void on_actionFind_unaccounted_renames_triggered(); void openRecentWorkspace(); void openRecentDatabase(); void updateWindowList(); void activateOtherWindow(); void invalidWorkspaceFormat(const QString &); + void readAttributes(const QModelIndex &); private: void closeEvent(QCloseEvent *); void switchMode(Mode); - Inventory *invModel; - Attributes *attrModel; - InventoryProxyModel *proxyModelFolderTree; - InventoryProxyModel *proxyModelFileList; + Inventory * invModel; + GetAttributes * attrModel; + InventoryProxyModel * proxyModelFolderTree; + InventoryProxyModel * proxyModelFileList; Mode mode; int closeCounter; }; ============================================================ --- src/view/TreeView.cpp 5b4f86399682825dd48a24084136979464fd5c69 +++ src/view/TreeView.cpp 3707234163333d91629f88fd1c84576610f942f0 @@ -73,7 +73,7 @@ void TreeView::contextMenuEvent(QContext ); setSelectionModel(selection); - // remove multiple cols of the same column, since we're selected + // remove multiple cols of the same row, since we're selected // complete rows we're not interested in these QModelIndexList currentSelection = selection->selectedIndexes(); for (int i=0; i < currentSelection.size(); i++) @@ -85,6 +85,21 @@ void TreeView::contextMenuEvent(QContext } } - emit contextMenuRequested(currentSelection, mapToGlobal(ev->pos())); + QPoint pos = ev->pos(); + + QHeaderView * headerView = header(); + if (headerView) + { + if (headerView->orientation() == Qt::Horizontal) + { + pos += QPoint(0, headerView->height()); + } + else + { + pos += QPoint(headerView->width(), 0); + } + } + + emit contextMenuRequested(currentSelection, mapToGlobal(pos)); } ============================================================ --- src/view/dialogs/ApplicationUpdate.cpp 7c2ecb7a44dcd75bd5d53e94efe173113df90547 +++ src/view/dialogs/ApplicationUpdate.cpp 5b6a164ff54ef8fabc95a3769c2b3c1fd2a878b3 @@ -39,7 +39,7 @@ ApplicationUpdate::ApplicationUpdate(QWi QHttp http; http.setHost("guitone.thomaskeller.biz"); - http.get("/appcast.xml"); + http.get("/web/appcast.xml"); SignalWaiter waiter(&http, SIGNAL(done(bool))); ============================================================ --- src/view/dialogs/CommitRevision.cpp ffe771b0f74202063686e3d30bcff6f6b9d0636a +++ src/view/dialogs/CommitRevision.cpp bba8d6475e14a2003cad91417b9bdd901ac15f55 @@ -174,24 +174,19 @@ void CommitRevision::accept() if (!committer.run()) { - C("Could not commit revision."); + QMessageBox::critical( + this, + tr("Could not commit revision"), + tr("Unable to commit the revision - this may be a bug in guitone " + "or a bug in monotone itself. Please take a closer look at the " + "logs and optionally send in a bug report."), + QMessageBox::Ok + ); + done(0); return; } - QMessageBox::information( - this, - tr("Revision committed"), - tr("The revision was successfully committed, however " - "since there is no update command available over automate yet, " - "your workspace cannot be updated automatically.\n" - "You need to do this by hand or check out the new revision:\n\n" - "\t%1\n\n" - "In any case close the current view to avoid committing the " - "same revision again.").arg(committer.getRevisionId()), - QMessageBox::Ok - ); - - done(0); + done(1); } void CommitRevision::revisionRead() ============================================================ --- src/view/dialogs/Dialog.cpp 926c311a65c0a2eeea6cae03256a56b8adbb7087 +++ src/view/dialogs/Dialog.cpp 4a9329c127de291fc2e8c236ee2ae3f97344bdfa @@ -28,14 +28,12 @@ Dialog::Dialog(QWidget * parent) : QDial Dialog::Dialog(QWidget * parent) : QDialog(parent) { - setAttribute(Qt::WA_MacMetalStyle); } Dialog::Dialog(QWidget * parent, QString objName) : QDialog(parent) { setObjectName(objName); - setAttribute(Qt::WA_MacMetalStyle); } Dialog::~Dialog() ============================================================ --- src/view/dialogs/FileDiff.cpp 3dae40899816900fb362793fef3deda6d2809e5c +++ src/view/dialogs/FileDiff.cpp 0df18f51a4020c5a2a36495b8da49fc5348c53c5 @@ -20,10 +20,10 @@ #include "FileDiff.h" #include "Monotone.h" -#include "SignalWaiter.h" #include "Settings.h" #include +#include FileDiff::FileDiff(QWidget* parent) : Dialog(parent) { @@ -31,79 +31,165 @@ FileDiff::FileDiff(QWidget* parent) : Di Dialog::init(); } -FileDiff::FileDiff(QWidget* parent, QString fileName) : Dialog(parent) +FileDiff::FileDiff(QWidget * parent, const QString & fileName, + const QString & base, const QString & target): Dialog(parent) { setupUi(this); Dialog::init(); - init(fileName); + init(fileName, base, target); } -void FileDiff::init(QString fileName) +void FileDiff::init( + const QString & fileName, const QString & base, const QString & target +) { - connect( - showVersionLeft, SIGNAL(toggled(bool)), - this, SLOT(versionToggled(bool)) - ); - connect( - showVersionRight, SIGNAL(toggled(bool)), - this, SLOT(versionToggled(bool)) - ); - connect( - showVersionBoth, SIGNAL(toggled(bool)), - this, SLOT(versionToggled(bool)) - ); + file = fileName; + loaded = false; QString title = windowTitle(); setWindowTitle(title.arg(fileName)); + if (base.isNull() || base.isEmpty()) + { + firstRevision->setText(tr("workspace parent")); + } + else + { + firstRevision->setText( + firstRevision->text().arg(base.left(12).append("...")) + ); + } + + if (target.isNull() || target.isEmpty()) + { + secondRevision->setText(tr("workspace revision")); + } + else + { + secondRevision->setText( + secondRevision->text().arg(target.left(12).append("...")) + ); + } + fileModel = new GetFile(this); - fileModel->readFileByName(fileName); fileProxyModel = new GetFileProxyModel(this); fileProxyModel->setSourceModel(fileModel); + diffModel = new ContentDiff(this); + diffView->setModel(fileProxyModel); diffStatusView->setModel(fileProxyModel); // make the line number col a little smaller diffView->header()->resizeSection(0, 40); + + connect( + firstRevision, SIGNAL(toggled(bool)), + this, SLOT(versionToggled(bool)) + ); + connect( + secondRevision, SIGNAL(toggled(bool)), + this, SLOT(versionToggled(bool)) + ); + connect( + bothRevisions, SIGNAL(toggled(bool)), + this, SLOT(versionToggled(bool)) + ); - SignalWaiter fileWaiter(fileModel, SIGNAL(fileRead())); + connect( + fileModel, SIGNAL(fileRead()), + this, SLOT(applyDiff()) + ); - if (fileWaiter.wait()) + connect( + diffModel, SIGNAL(diffRead()), + this, SLOT(applyDiff()) + ); + + connect( + scrollToNext, SIGNAL(clicked()), + this, SLOT(getNextGroup()) + ); + + connect( + scrollToPrev, SIGNAL(clicked()), + this, SLOT(getPrevGroup()) + ); + + fileModel->readFileByName(fileName, base); + diffModel->readDiff(fileName, base, target); +} + +FileDiff::~FileDiff() {} + +void FileDiff::applyDiff() +{ + if (!loaded) { - diffModel = new ContentDiff(this); - diffModel->readDiff(fileName); - - SignalWaiter diffWaiter(diffModel, SIGNAL(diffRead())); - - if (diffWaiter.wait()) - { - fileModel->applyDiff(diffModel->getDiff(fileName)); - diffStatusView->update(); - } - else - { - C("diffWaiter timed out"); - } + loaded = true; + return; } - else + + Diff * diff = diffModel->getDiff(file); + if (diff == 0) { - C("fileWaiter timed out"); + QMessageBox::information( + this, + tr("File has not changed"), + tr("The file has not been changed between these revisions."), + QMessageBox::Ok + ); + reject(); + return; } + + fileModel->applyDiff(diff); + diffStatusView->update(); } -FileDiff::~FileDiff() {} - void FileDiff::versionToggled(bool dummy) { - if (showVersionLeft->isChecked()) + if (firstRevision->isChecked()) fileProxyModel->setFileVersion(GetFileProxyModel::Left); - if (showVersionRight->isChecked()) + if (secondRevision->isChecked()) fileProxyModel->setFileVersion(GetFileProxyModel::Right); - if (showVersionBoth->isChecked()) + if (bothRevisions->isChecked()) fileProxyModel->setFileVersion(GetFileProxyModel::Both); diffStatusView->update(); } +void FileDiff::scrollToGroup(bool forward) +{ + if (!currentIndex.isValid()) return; + QModelIndex proxyIndex = fileProxyModel->mapFromSource(currentIndex); + + // recursion: if the queried group is not valid in the view because it + // has been filtered out, get the next group and try again + if (!proxyIndex.isValid()) + { + if (forward) + { + getNextGroup(); + return; + } + getPrevGroup(); + return; + } + + diffView->scrollTo(proxyIndex, QAbstractItemView::PositionAtTop); +} + +void FileDiff::getPrevGroup() +{ + currentIndex = fileModel->getPrevGroup(currentIndex); + scrollToGroup(false); +} + +void FileDiff::getNextGroup() +{ + currentIndex = fileModel->getNextGroup(currentIndex); + scrollToGroup(true); +} + ============================================================ --- src/view/dialogs/FileDiff.h dcba92805d020b9d0d83de903f27abda0a5a7c74 +++ src/view/dialogs/FileDiff.h c254fa1cdec0a6fe6545adcf784e5cbcb5ca1573 @@ -32,19 +32,26 @@ public: Q_OBJECT public: - FileDiff(QWidget*); - FileDiff(QWidget*, QString); - void init(QString); + FileDiff(QWidget *); + FileDiff(QWidget *, const QString &, const QString & base = QString(), const QString & target = QString()); + void init(const QString &, const QString & base = QString(), const QString & target = QString()); ~FileDiff(); private slots: + void applyDiff(); void versionToggled(bool); - + void scrollToGroup(bool); + void getPrevGroup(); + void getNextGroup(); + private: ContentDiff * diffModel; GetFile * fileModel; GetFileProxyModel * fileProxyModel; + QModelIndex currentIndex; + QString file; + bool loaded; }; #endif ============================================================ --- src/view/dialogs/Preferences.cpp c34d5f6dce4113366c6d807b1b0a4bb97d3350b9 +++ src/view/dialogs/Preferences.cpp 4f3d46e215748754366bafc460894a91b6febc34 @@ -41,12 +41,19 @@ Preferences::Preferences(QWidget* parent enableFileLog->setText(enableFileLog->text().arg(DebugLog::logFilePath())); - logLevel->addItem(tr("Low (fatal errors)"), 1); - logLevel->addItem(tr("Medium (critical errors)"), 2); - logLevel->addItem(tr("High (warnings)"), 3); - logLevel->addItem(tr("All (debug messages)"), 4); + logLevel->addItem(tr("Very low (only fatal)"), 1); + logLevel->addItem(tr("Low (critical)"), 2); + logLevel->addItem(tr("Medium (warnings)"), 3); + logLevel->addItem(tr("High (info messages)"), 4); - logLevel->setCurrentIndex(DebugLog::getLogLevel()-1); + int lvl = DebugLog::getLogLevel(); +#ifndef QT_NO_DEBUG + logLevel->addItem(tr("Very high (debug messages)"), 5); +#else + lvl = lvl > 4 ? 4 : lvl; +#endif + + logLevel->setCurrentIndex(lvl - 1); checkForUpdates->setCheckState( Settings::getBool("CheckForUpdates", true) ? ============================================================ --- src/vocab.h dbf6b8075bfee39d1c9494bf7fbc58b842904213 +++ src/vocab.h afe123e8771f70b90c89dc2a9cddeb6206f81142 @@ -5,38 +5,58 @@ // global macros and defines // +// if you use any of those two, you also have to include Guitone.h class Guitone; #define APP reinterpret_cast(qApp) #define MTN(arg) APP->getMonotoneInstance(arg) -// FIXME: uncomment the following lines if you have compile problems -// on Windows using MSVC++ -/* -#ifdef _MSC_VER -#define __FUNCTION__ "" -#endif -*/ - +#include "DebugLog.h" #ifdef QT_NO_DEBUG -#define D(arg) void() +#define D(msg) void(msg) #else -#define D(arg) qDebug("%s:%s:%d: %s", __FILE__, __FUNCTION__, __LINE__, qPrintable(QString(arg))) +#define D(msg) DebugLog::debug(QString("%1:%2:%3: %4") \ + .arg(__FILE__).arg(__FUNCTION__).arg(__LINE__).arg(QString(msg))) #endif -#define W(arg) qWarning("%s:%s:%d: %s", __FILE__, __FUNCTION__, __LINE__, qPrintable(QString(arg))) -#define C(arg) qCritical("%s:%s:%d: %s", __FILE__, __FUNCTION__, __LINE__, qPrintable(QString(arg))) -#define F(arg) qFatal("%s:%s:%d: %s", __FILE__, __FUNCTION__, __LINE__, qPrintable(QString(arg))) -#define I(arg) if (!(arg)) qFatal("%s:%s:%d: invariant \"%s\" violated", __FILE__, __FUNCTION__, __LINE__, #arg) +#define L(msg) DebugLog::info(QString("%1:%2:%3: %4") \ + .arg(__FILE__).arg(__FUNCTION__).arg(__LINE__).arg(QString(msg))) +#define W(msg) DebugLog::warn(QString("%1:%2:%3: %4") \ + .arg(__FILE__).arg(__FUNCTION__).arg(__LINE__).arg(QString(msg))) +#define C(msg) DebugLog::critical(QString("%1:%2:%3: %4") \ + .arg(__FILE__).arg(__FUNCTION__).arg(__LINE__).arg(QString(msg))) +#define F(msg) { \ + DebugLog::fatal(QString("%1:%2:%3: %4") \ + .arg(__FILE__).arg(__FUNCTION__).arg(__LINE__).arg(QString(msg))); \ + abort(); \ + } +#define I(expr) if (!(expr)) F(QString("invariant \"%1\" violated").arg(#expr)) + // // type definitions // +#include #include #include +#include // used for manifest entries, if the bool var is true, the entry is a directory -typedef QPair FileEntry; +struct FileEntry { + FileEntry() {} + FileEntry(QString p, bool d) : path(p), is_dir(d) {} + FileEntry(QString p, bool d, QString f) : path(p), is_dir(d), fileid(f) {} + QString path; + bool is_dir; + QString fileid; + QMap attrs; + + inline bool operator<(const FileEntry & other) const + { + return path < other.path; + } +}; + typedef QList FileEntryList; // used for revision certs @@ -47,4 +67,18 @@ typedef QList ByteArrayList; typedef QList ByteArrayList; +// used for BasicIOParser and BasicIOWriter +struct StanzaEntry { + StanzaEntry() {} + StanzaEntry(QString s, QString h) : sym(s), hash(h) {} + StanzaEntry(QString s, QStringList v) : sym(s), vals(v) {} + + QString sym; + QString hash; + QStringList vals; +}; + +typedef QList Stanza; +typedef QList StanzaList; + #endif ============================================================ --- tests/test.cpp 1bc1c23455d91468e22f229dffa8fcee9277a8a9 +++ tests/test.cpp 8c345925e352aea61e5f51a0b2ad01fb6475c282 @@ -1,10 +1,18 @@ +#include + +#define TEST(name, argc, argv) QTest::qExec(new name(), argc, argv); + #include "TestTest.h" +#include "StdioParserTest.h" +#include "MonotoneTest.h" int main(int argc, char** argv) { QCoreApplication app(argc, argv); - QTest::qExec(&TestTest(), argc, argv); + TEST(TestTest, argc, argv); + TEST(StdioParserTest, argc, argv); + TEST(MonotoneTest, argc, argv); } ============================================================ --- tests/test.pro 5005bf499b91c8cf6e51e3c74915ee1d9069a312 +++ tests/test.pro b437a3ca1b89faa23f2665324caa1a2f121184a3 @@ -1,16 +1,25 @@ -TEMPLATE = app -TARGET = test -CONFIG += qt debug qtestlib +TEMPLATE = app +TARGET = test +CONFIG += qtestlib +macx:CONFIG -= app_bundle INCLUDEPATH = . \ - ../guitone/src/ \ - ../guitone/src/view \ - ../guitone/src/view/dialogs \ - ../guitone/src/model \ - ../guitone/src/monotone \ - ../guitone/src/util -HEADERS += TestTest.h -SOURCES += test.cpp + ../src/ \ + ../src/view \ + ../src/view/dialogs \ + ../src/model \ + ../src/monotone \ + ../src/util +HEADERS += TestTest.h \ + StdioParserTest.h \ + MonotoneTest.h \ + ../src/monotone/Monotone.h \ + ../src/util/SignalWaiter.h +SOURCES += test.cpp \ + ../src/util/StdioParser.cpp \ + ../src/util/AbstractParser.cpp \ + ../src/monotone/Monotone.cpp \ + ../src/util/SignalWaiter.cpp OBJECTS_DIR = tmp MOC_DIR = tmp