diff options
Diffstat (limited to 'cervisia')
175 files changed, 25581 insertions, 0 deletions
diff --git a/cervisia/COPYING b/cervisia/COPYING new file mode 100644 index 00000000..96bdc086 --- /dev/null +++ b/cervisia/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + 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. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/cervisia/ChangeLog b/cervisia/ChangeLog new file mode 100644 index 00000000..6461e67f --- /dev/null +++ b/cervisia/ChangeLog @@ -0,0 +1,1543 @@ +2008-08-15 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR #162523: + Allow cvsnt users to login to repositories. + +2008-06-18 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR #164216: + Fixed crash when updating the status after adding a directory to a repository. + +2007-09-11 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR #148162: + Workaround a regression in Qt 3.3.8 QDateTime::fromString(). + Patch by Martin Koller. + +2007-01-13 André Wöbbeking <Woebbeking@kde.org> + + * Fix: + Handle '.' in user name when adding a repository. + +2006-09-02 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR #133129: + Dates from cvs history are parsed correctly. + + While at it also made parsing more robust and added event 'P' + "Update, Patched". + +2006-08-12 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR #131695: + Broken repository locations don't crash cvsservice anymore. + +2005-08-03 Christian Loose <christian.loose@kdemail.net> + + * Implemented wish #92938: + Added ability to exclude files in the commit dialog from + the subsequent commit. + +2005-03-30 Christian Loose <christian.loose@kdemail.net> + + * Change License to GPL. + +2005-03-17 Christian Loose <christian.loose@kdemail.net> + + * Implemented wish #40760: + New item in context menu to fold/unfold the + selected folder and its subfolders. + +2005-03-07 Christian Loose <christian.loose@kdemail.net> + + * Added support for commit template message (CVS/Template) in + the commit dialog. Patch by Darrell Esau. + +2005-03-04 Christian Loose <christian.loose@kdemail.net> + + * Fix BR #97664: + Fix statusbar when embedded in Konqueror. + +2005-01-31 Christian Loose <christian.loose@kdemail.net> + + * Implemented wish #95257: + Added new setting for a color. It is used to highlight files in + the update view with status "Not in CVS". + +2005-01-09 André Wöbbeking <Woebbeking@kde.org> + + * Added an icon for "Diff". Thanks to Jonathan Riddell. + +2004-12-08 Christian Loose <christian.loose@kdemail.net> + + * Fix BR #90346: + Normalize user-entered CVSROOT specification before adding + a new group to the cvsservicerc configuration file. This + prevents duplicate entries in the repository list. + +2004-11-30 Christian Loose <christian.loose@kdemail.net> + + * Fix BR #94083: + Don't crash while removing old 'Edit with...' menu item + from the context menu. This can happen after the user + switched tabs in Konqueror. + +2004-11-11 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR #92576: + Use correct encoding for status messages. + +2004-10-26 Christian Loose <christian.loose@kdemail.net> + + * Implemented wish #78696: + Added possibility to get a notification, when a cvs commit job + has finished. + +2004-10-14 André Wöbbeking <Woebbeking@kde.org> + + * Added a search line (ala JuK and KMail) to the CVS log list view. + +2004-09-10 Christian Loose <christian.loose@kdemail.net> + + * Fix bug #89215: + Always make sure that directory entries '.' and '..' + are part of the ignore list to prevent an endless loop + in UpdateDirItem::maybeScanDir(). + +2004-09-09 Christian Loose <christian.loose@kdemail.net> + + * Added new command-line option -annotate. + +2004-08-30 Christian Loose <christian.loose@kdemail.net> + + * Added new item 'Properties' to context menu. It + shows the properties dialog for the selected file. + +2004-08-28 Christian Loose <christian.loose@kdemail.net> + + * Implemented wish #74751: + Added support for non-recursive checkouts to the + checkout dialog. Patch by Sergio Visinoni. + +2004-08-23 Christian Loose <christian.loose@kdemail.net> + + * Implemented bug #74862: + In the checkout dialog, it's now possible to fetch + the existing branch/tag names for a module from the + cvs server. + * Fix bug #87830: + Always read the cvs client option from the configuration + file even when there is no sandbox open. + +2004-07-13 André Wöbbeking <Woebbeking@kde.org> + + * Implemented FR #67805, #67806, #67807, #67809: + Hooray, icons for Cervisia's (main) actions. + Thanks to Marco Martin for the great artwork! + +2004-07-11 Christian Loose <christian.loose@kdemail.net> + + * Fix bug #83239: + Fixed retrieving author from cvs log output. + +2004-07-09 Christian Loose <christian.loose@kdemail.net> + + * Implemented wish #75825: + The context menu for the file view gained an + 'Edit With' menu. It's now possible to start a + different application for the selected file. + +2004-07-07 Christian Loose <christian.loose@kdemail.net> + + * Implemented wish #75017: + Show the patch option dialog when the user called the + 'Create patch against repository' action and pass + the selected options to the 'cvs diff' command. + +2004-07-05 Christian Loose <christian.loose@kdemail.net> + + * Added a new patch option dialog. This lets you choose + the options passed to the diff command when creating + a patch with the 'Create Patch' button in the log + dialog. + +2004-06-21 Christian Loose <christian.loose@kdemail.net> + + * Implemented wish #66231: + Added new 'Create Patch' button to the log dialog. This + feature makes it possible to create a patch between + arbitrary CVS revisions. + +2004-06-02 Christian Loose <christian.loose@kdemail.net> + + * Implemented wish #77894: + Added support for a checkout of a module without + the CVS folder. (cvs export) + Patch by Dermot Daly + + * Implemented wish #80177: + It's now possible to checkout a project under an + alias name. (cvs checkout -d) + Patch by Dermot Daly + +2004-05-29 Christian Loose <christian.loose@kdemail.net> + + * Implemented wish #63592: + Honor the CVSROOT/cvsignore file by downloading it from the + cvs server and adding it to the global ignore list. + +2004-05-29 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR #81665: don't show duplicated files in the file view (did + only occur when the option "Update Recursively" wasn't active). + +2004-05-21 Christian Loose <christian.loose@kdemail.net> + + * Added a new method to download the CVSROOT/cvsignore file + from the cvs server to the DCOP service. + +2004-05-17 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR #58254: honor option "Hide Non-CVS Files" when opening + a branch in the file tree. + +2004-05-17 Christian Loose <christian.loose@kdemail.net> + + * Implemented wish #41467: + Added possibility to hide files with status Unknown by + extending the current option "Hide Up-To-Date Files". This option + is now called "Hide Unmodified Files". + +2004-05-16 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR #81498: handle spaces in the working folder name correctly. + +2004-05-07 Christian Loose <christian.loose@kdemail.net> + + * Added auto completion to the working folder line edit + in the checkout and the import dialog. + +2004-05-05 Christian Loose <christian.loose@kdemail.net> + + * Added support for compression levels (-z) above three. + +2004-04-29 Christian Loose <christian.loose@kdemail.net> + + * Activated spellchecking in changelog dialog. + * Implement BR #79957: + Spellchecking in commit dialog. + (patch by theboywho@ruddyperl.com) + +2004-04-23 Christian Loose <christian.loose@kdemail.net> + + * Big cleanup - removed KDE 3.1 support + +2004-04-14 Christian Loose <christian.loose@kdemail.net> + + * Fix session management + +2004-04-13 Christian Loose <christian.loose@kdemail.net> + + * Implemented BR #74754: + Added support for 'cvs init' to create a new repository. + +2004-04-01 Christian Loose <christian.loose@hamburg.de> + + * Fix BR #46871: + Preserve file content in resolve dialog: + - don't remove characters + - don't add or remove new line markers + - handle A+B/B+A cases with no new line marker at + the end of the first version correctly + * Fix BR #74903: + Don't choke on conflict markers that are not on a + separate line in the resolve dialog. This happens + when the file didn't end with a new line marker + before CVS encountered the conflict. + * Fix BR #78800: + Lock harder whether a directory really is under CVS control. + Fix by Frerich Raabe. + +2004-03-17 Christian Loose <christian.loose@hamburg.de> + + * Don't execute shell scripts or .desktop files when the user used + the edit file function. (BR #77440) + +2004-03-02 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR #55871: + - truncate the tooltip text if necessary + - use a subclassed QToolTip instead of the own TipLabel. + +2004-03-01 Christian Loose <christian.loose@hamburg.de> + + * Implemented BR #72861: + Added support for option "use the file's modification time as the time + of import" (-d) to the cvs import function. + +2004-02-24 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR #75201: + Prevent crash when you activate a CervisiaPart view with RMB in + a Koqueror with more than one view. + +2004-02-20 Christian Loose <christian.loose@hamburg.de> + + * Fix remembering the last input values in checkout dialog. + +2004-02-01 Christian Loose <christian.loose@hamburg.de> + + * LogTree now derives from QTable instead of the deprecated QtTableView + +2004-01-22 Christian Loose <christian.loose@hamburg.de> + + * Fix BR #70936: + Prevent crash while embedded into Quanta because of + a name conflict between the TagDialog classes. + +2004-01-16 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR 72519 (file view): + Don't select hidden files when you select a range of files + with Shift key. This prevents you from evil accidents (i.e. + "Remove from Repository"). + +2003-10-04 Christian Loose <christian.loose@hamburg.de> + + * Do a cvs logout when a user removes a pserver repository item. + This way the repository isn't re-added because of the .cvspass file. + +2003-10-03 Christian Loose <christian.loose@hamburg.de> + + * Added support to add or remove watches to the cvs DCOP service. + +2003-09-14 Christian Loose <christian.loose@hamburg.de> + + * Added new function makePatch() to the cvs DCOP service + to create a patch against the repository. + +2003-09-05 Christian Loose <christian.loose@hamburg.de> + + * Implemented BR #56716: + Added login/logout functionality for pserver cvs + servers. + +2003-08-30 Christian Loose <christian.loose@hamburg.de> + + * Added editors() and import() methods to cvs DCOP service + +2003-08-29 Christian Loose <christian.loose@hamburg.de> + + * Removed the editor option from the settings dialog. We + use KRun now to start the preferred editor for the given + mime-type. + * Fix BR #53815: Prevent the user from changing the directory + with konqueror's tree view while there is a job running + in the protocol view. + +2003-08-28 Christian Loose <christian.loose@hamburg.de> + + * Added a new button (View) to the log dialog to view + the selected revision of a file in the preferred editor. + * Added method downloadRevision() to the cvs DCOP service + to download a specific revision of a file. + +2003-08-27 Christian Loose <christian.loose@hamburg.de> + + * Revamped settings dialog: + - Used KJanusWidget::IconList instead of KJanusWidget::Tabbed + - Merged 'Appearance' and 'Colors' page + - Moved creation of option pages in separate methods + +2003-08-09 André Wöbbeking <Woebbeking@kde.org> + + * Make file view configurable (column order/widths, sorting). + +2003-08-02 André Wöbbeking <Woebbeking@kde.org> + + * Fix/Implement FR 56042: + Use the configured colors as foreground colors in the file AND + protocol view and a bold font to improve readability of the text + for modified, added and removed files. + +2003-07-30 Christian Loose <christian.loose@hamburg.de> + + * Added lock() and unlock() methods to the DCOP service. + * Replace the lock and unlock implementation in CervisiaPart + by calls to the new methods in the DCOP service. + +2003-07-28 Christian Loose <christian.loose@hamburg.de> + + * Add search functionality to the plain log view. Now you + can search for a word in the commit messages. + +2003-07-16 Christian Loose <christian.loose@hamburg.de> + + * Added new view variant for cvs' log output to the log dialog. + This view shows the data in a format that is very similar to + the format of the command-line output of cvs log. + +2003-07-09 Christian Loose <christian.loose@hamburg.de> + + * New watchers dialog: Cervisia now shows the watchers of + the selected files in a nice dialog instead of just showing cvs' + output in the protocol view. + +2003-07-07 Christian Loose <christian.loose@hamburg.de> + + * Added createTag() and deleteTag() methods to the DCOP + service and use them in the part. + +2003-07-04 Christian Loose <christian.loose@hamburg.de> + + * Implement BR #60604: It's now possible to select + revision B in Log dialog with Ctrl key + left mouse button in + addition to the middle mouse button. + +2003-06-26 Christian Loose <christian.loose@hamburg.de> + + * Start/Stop the ssh-agent process and setup the cvs + job environment to use it. + * Initial version of our own little ssh-askpass program. + * Initial version of the SshAgent class which will later enable + the cvs DCOP service to utilize the ssh-agent program. + +2003-06-19 Christian Loose <christian.loose@hamburg.de> + + * Added showWatchers() method to the DCOP service. + +2003-06-19 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR 59275 (DiffZoomWidget): + o Use QStyle::querySubControlMetrics() to get the exact + geometry of the scroll bar groove. + o Better performance for big files. + +2003-06-14 Christian Loose <christian.loose@hamburg.de> + + * Implement BR #59644: Change key shortcuts for cvs add and + cvs remove to Insert and Delete. Now you can use the plus and + minus keys for the tree view. + +2003-06-08 Christian Loose <christian.loose@hamburg.de> + + * Added possibility to DCOP service to retrieve the accumulated + output after cvs command finished. + +2003-06-04 Christian Loose <christian.loose@hamburg.de> + + * Added new command line option to show a log dialog for + a given file without starting the whole program. + Usage: cervisia -log main.cpp + +2003-06-03 Christian Loose <christian.loose@hamburg.de> + + * Fix BR #59267: Re-added "clear" command to the + RMB context menu of the ProtocolView + +2003-05-29 Christian Loose <christian.loose@hamburg.de> + + * Font of ChangeLog dialog now configurable. + +2003-05-25 Christian Loose <christian.loose@hamburg.de> + + * Added a method to retrieve a list of modules in the repository + to the cvs DCOP service (cvs checkout -c). + * Use the new method in the CheckoutDialog. + +2003-05-22 Christian Loose <christian.loose@hamburg.de> + + * Added edit() and unedit() method to cvs DCOP service + and used them for the corresponding actions in + Cervisia. + +2003-05-14 Christian Loose <christian.loose@hamburg.de> + + * Use DCOP service for history action + * Added history() method to cvs DCOP service + +2003-05-10 André Wöbbeking <Woebbeking@kde.org> + + * Fix BR 50918: + o Added possibility to diff a selected file against the + newest version in the repository (cvs diff -r HEAD). + o Now External and internal diff show the same differences. + +2003-05-09 Christian Loose <christian.loose@hamburg.de> + + * Use DCOP service for commit action + (CervisiaPart::slotCommit()) + * Convert CervisiaPart::updateActions() from manually + changing the state of the menu items to using + KXMLGUIClient::stateChanged() with the corresponding + setup in the part's rc-file. + +2003-05-05 André Wöbbeking <Woebbeking@kde.org> + + * Diff between any revision and the sandbox (only select + revision A in the log dialog). + +2003-04-18 Christian Loose <christian.loose@hamburg.de> + + * Fix BR 56042: Better default colors for white + background + * Fix BR 56942: Escape output lines for protocol view + so html tags in commit messages aren't interpreted + +2003-02-28 Christian Loose <christian.loose@hamburg.de> + + * Fix BR #54382: Display warning message in remove dialog + to make clear that the action will also remove the local + copy of the selected files. + +2003-02-24 Christian Loose <christian.loose@hamburg.de> + + * Make shortcut keys of actions which are part of CervisiaPart + configurable (#55125). + +2003-02-21 Christian Loose <christian.loose@hamburg.de> + + * Implemented BR #41263: Added splitters to resolve + dialog. + +2003-02-18 Christian Loose <christian.loose@hamburg.de> + + * Big cleanup of parseCvsDiff() in diff dialog + +2003-02-17 Christian Loose <christian.loose@hamburg.de> + + * Use DCOP service for diff dialog. + * Added private method callExternalDiff() in diff + dialog to make parseCvsDiff() more readable. + +2003-02-16 Christian Loose <christian.loose@hamburg.de> + + * Improved size handling for Repository dialog, + Add repository dialog and Checkout dialog. + +2003-02-12 Christian Loose <christian.loose@hamburg.de> + + * Bug 54106: Display error message when user tries to + access a remote repository + * Improved size handling for Commit dialog, History dialog, + Resolve dialog and Resolve edit dialog + * Removed code duplication in Commit dialog when + displaying the Diff dialog + +2003-02-11 Christian Loose <christian.loose@hamburg.de> + + * Improved size handling for Diff dialog + +2003-02-10 Christian Loose <christian.loose@hamburg.de> + + * Remove CervisiaShell's dependency on CervisiaPart + * Moved filter status indicator to CervisiaPart + * Improve dialog size handling for ChangeLog dialog + (use new KDialogBase methods to save the size into + CervisiaPart's configuration file) + +2003-02-06 Christian Loose <christian.loose@hamburg.de> + + * Try to get the user name and email address for the + changelog from the control center settings (KEMailSettings) + before asking the system. + +2003-02-02 Christian Loose <christian.loose@hamburg.de> + + * Added login() and logout() methods to DCOP service. + +2003-01-26 André Wöbbeking <Woebbeking@kde.org> + + * Implemented new option "Hide Empty Directories" + +2003-01-23 Christian Loose <christian.loose@hamburg.de> + + * Implemented wish #41468: Remember last open directory + in KDirSelect dialog + +2003-01-21 Christian Loose <christian.loose@hamburg.de> + + * Added remove() method to cvs DCOP service + * Use DCOP service for cvs add and cvs remove + +2003-01-18 Christian Loose <christian.loose@hamburg.de> + + * Remove custom dialog size handling from annotation + dialog. The size is stored globally to simulate + Cervisia's old behaviour. + +2003-01-17 Christian Loose <christian.loose@hamburg.de> + + * Remove restorePseudo() hack + * We always want to save "Current Directory". So move out + of the session management methods. + * Settings for the part are now handle by the part. This + fixes partly the bug #38235. + +2003-01-16 Christian Loose <christian.loose@hamburg.de> + + * Changed main window size handling. The window size is now + handled by KMainWindow. + * Added method openURL() to CervisiaShell. Use this method to + open the sandbox which was provided on the command line. + TODO: remove restorePseudo() + +2003-01-11 Christian Loose <christian.loose@hamburg.de> + + * Added help button to CommitDialog + * Use DCOP service to retrieve Tags and Branches for TagDialog, + MergeDialog und UpdateDialog + * Added new AddRemoveDialog (extracted from CommitDialog) + * Make functionality to view diff in CommitDialog more visible + by adding diff button. + +2003-01-11 André Wöbbeking <Woebbeking@kde.org> + + * unfoldTree(): reduced flicker and improved perfomance by disabling updates. + +2003-01-11 André Wöbbeking <Woebbeking@kde.org> + + * Moved colors from UpdateViewItem to UpdateView: + o it's faster as you don't need KConfig in UpdateViewItem ctor + o it needs less memory (3 colors per item) + +2003-01-09 Christian Loose <christian.loose@hamburg.de> + + * Use DCOP stubs to access the methods of the cvs DCOP service + * Added new method update() and checkout() to DCOP service + * Use KProcess::operator<< instead of QString::operator+= to + build the command line + * Make CVS_SERVER configurable in DCOP service + (GUI is missing) + * Break up updateOrStatus() method in cervisiapart.cpp + * Use DCOP service to update the working copy + +2003-01-04 Christian Loose <christian.loose@hamburg.de> + + * Use DCOP service to update status for UpdateView + +2003-01-02 Christian Loose <christian.loose@hamburg.de> + + * Added new startJob() method to ProtocolView + that uses the new cvs DCOP service + +2003-01-02 André Wöbbeking <Woebbeking@kde.org> + + * Replaced deprecated Qt classes/methods with actual equivalents. + +2002-12-31 Christian Loose <christian.loose@hamburg.de> + + * Separate GUI from functionality for AnnotateDialog + * Use DCOP service for log dialog + +2002-12-30 André Wöbbeking <Woebbeking@kde.org> + + * Use user's settings (locale and timezone) to display dates. + +2002-12-29 André Wöbbeking <Woebbeking@kde.org> + + * Fixed sorting in list views. + +2002-12-29 Christian Loose <christian.loose@hamburg.de> + + * Extract AddRepositoryDialog from repositorydlg.cpp + * Save and read repository configuration to/from the + configuration file of the cvs DCOP service + * Added kconf_update script to copy repository configuration + to cvsservicerc + +2002-12-28 Christian Loose <christian.loose@hamburg.de> + + * Start and stop cvs DCOP service in CervisiaPart + * Change working copy directory in DCOP service + * Use DCOP service for annotate dialog + * Several changes to the DCOP service + * Added new parseCvsLog() method to LogDialog that + uses the new cvs DCOP service + * Save and read cvs client and global compression level configuration + to/from the configuration file of the cvs DCOP service + * Added kconf_update script + +2002-12-26 André Wöbbeking <Woebbeking@kde.org> + + * Fixed parsing of branch and tag names: + o no more trailing spaces + o names with more than 24 chars are identified now + +2002-12-23 André Wöbbeking <Woebbeking@kde.org> + + * All dialogs: + - Inherit from KDialogBase instead of QDialog + -> less code and more KDE standard compliant. + - removed layout leftovers from old Qt versions. + - reduced header dependences. + +2002-12-19 Christian Loose <christian.loose@hamburg.de> + + * Replace deprecated QMultiLineEdit with KTextEdit in + ChangeLog dialog + * Added new parseCvsAnnotate() method to AnnotateDialog + that uses the new cvs DCOP service + +2002-12-18 Christian Loose <christian.loose@hamburg.de> + + * Added a new progress dialog that will replace + CvsProgressDialog in the near future + +2002-12-17 Christian Loose <christian.loose@hamburg.de> + + * Fixed "fetch of branch list hangs in update dialog" (#50824) + +2002-12-14 Christian Loose <christian.loose@hamburg.de> + + * Added first version of cvs DCOP service + +2002-12-14 André Wöbbeking <Woebbeking@kde.org> + + * Removed ListViewItem. Use Q/KListViewItem instead. + +2002-12-14 André Wöbbeking <Woebbeking@kde.org> + + * ProtocolView::appendLine(): removed trailing <br> + as QTextEdit::append() already adds a new paragraph. + Detect "U " as remote changed file. + +2002-12-12 Christian Loose <christian.loose@hamburg.de> + + * Fix the too small scroll area of diff view when + tabs in source code + +2002-12-08 Christian Loose <christian.loose@hamburg.de> + + * Make ChangeLog dialog more KDE standard conformant + QDialog -> KDialogBase + +2002-12-05 Christian Loose <christian.loose@hamburg.de> + + * Don't add a new line to the end of changelog + * Use QFile instead of FILE* and remove unneeded + inheritance in cvsdir.cpp + +2002-12-02 Christian Loose <christian.loose@hamburg.de> + + * Fixed commit dialog bigger than screen bug (#50735) + +2002-11-29 Christian Loose <christian.loose@hamburg.de> + + * Fixed automatic cvs edit option which called cvs edit + for more files than necessary + * Added support for new .cvspass format introduced with + cvs 1.11.1 + * Fixed checkout dialog bigger than screen bug + * Fixed sort order of the revision numbers in log view + +2002-07-25 Christian Loose <christian.loose@hamburg.de> + + * Use KGenericFactory + * Revived old filter status indicator in statusbar + * Preserve empty lines in ChangeLog while adding a + new entry + +2002-06-28 Bernd Gehrmann <bernd@mail.berlios.de> + + * Removed Qt1-specific layout management stuff + * More latin1 conversions removed + * Command line option --resolve filename + (Stanislav Visnovsky <visnovsky@nenya.ms.mff.cuni.cz>) + +2002-06-27 Bernd Gehrmann <bernd@mail.berlios.de> + + * Mark files with option -kb with a binary icon + * Fixed restoration of the last loaded sandbox + * Use different instance names for part and shell, + otherwise KConfig get messed up + +2002-06-26 Bernd Gehrmann <bernd@mail.berlios.de> + + * Made editor configurable again + * Readded manpage + * Put help buttons in all dialogs, linked to + the online docs + +2002-06-25 Bernd Gehrmann <bernd@mail.berlios.de> + + * Fixed char buffer to QString conversion in + CvsProgressDialog, resulting in random garbage + inserted in the annotate view. Also a bug + introduced in 2002-06-11 + * Changed annotate view to QListView. Based + on a patch by André Wöbbeking <Woebbeking@kde.org> + * Hide custom tooltips in list views when contents + are scrolled + * Simplified history dialog filtering by + using QListViewItem::setVisible() + * #include cleanup + * i18n fixes + * Rewritten repository settings dialog; its + functionality now comprises that of the former + dialog and the former add repository dialog + * Load .ui, .docbook and .xml files with utf8 + encoding. This can be implemented in a cleaner way + * Resolved accelerator conflict in resolve dialog + * Escape all text inserted in richtext tooltips + +2002-06-22 Bernd Gehrmann <bernd@mail.berlios.de> + + * Fixed diff dialog bug due to changes from 2002-06-11 + +2002-06-18 Roland Krause <rokrau@yahoo.com> + + * View Filter is now applied after Fold/Unfold of the + file tree. + +2002-06-11 Bernd Gehrmann <bernd@mail.berlios.de> + + * Interpret all output from cvs in the user's locale + +2002-04-28 Bernd Gehrmann <bernd@mail.berlios.de> + + * Little layout fix in log dialog by Christian Loose + * Patch by Andrew Speer <andrew.speer@isolutions.com.au>: + in tags list (produced by cvs status -v) accept tabs + as delimiter + +2002-04-22 Bernd Gehrmann <bernd@mail.berlios.de> + + * Patch by Christian Loose <christian.loose@hamburg.de>: + - Allow to specify a comment when importing a module + - Enable checkout/import when no item is selected + * Always enable folding and unfolding the tree + +2002-04-17 Bernd Gehrmann <bernd@mail.berlios.de> + + * Patch by Gregory Green <gregory.p.green@boeing.com>: + - Checkout of branches + * Patch by Roland Krause <rokrau@yahoo.com>: + - Added "Last change" to context menu + - Added filter for files which are not in cvs + * Ignore stderr in Make Patch (it would produce + invalid patches previously) + +2002-04-03 Bernd Gehrmann <bernd@mail.berlios.de> + + * Release 1.5rich + +2002-02-04 Bernd Gehrmann <bernd@mail.berlios.de> + + * Colored ProtocolView output + Based on a patch by Asaf Gery <asaf@telmap.com> + +2001-09-04 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Added 'Force tag creation' option to tag dialog + Patch by Alessandro Praduroux <pradu@thekompany.com> + + ======> TODO Before Importing + ======================================== + * Remove stale CVS directories and add to main repository + kdesdk/cervisia/CVS + kdesdk/po/cervisia/CVS + kdesdk/doc/cervisia/CVS + + cd kdesdk + cvs add cervisia + cd cervisia + cvs add -kb *.png + cvs add Makefile.am README TODO .cvsignore ChangeLog LICENSE.QPL \ + cervisia.lsm *.cpp *.h *.rc + ======================================== + + +2001-09-01 Richard Moore <rich@kde.org> + + * Added support for KDE/Qt 3. At the moment I've just made the + minimum set of changes required to make it compile. Qt 2.x is + still supported of course. + * Moved README, TODO, ChangeLog, cervisia.lsm and LICENSE.QPL to + the main source directory. + * Moved version string from configure.in.in to version.h + * Updated version string + * Added a kdoc build target to Makefile.am so we can generate some + api documentation + * Imported to kdesdk CVS module + +2001-07-05 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Added missing icons to doc/common directory + +2001-07-04 Richard Moore <rich@kde.org> + + * Converted to KParts + * Created a standalone shell app that embeds the part + * Converted Makefile to use METASOURCES = AUTO + +2001-06-23 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Avoid making NotInCVS files up-to-date + * Don't show attic directories + * DEBUGOUT -> kdDebug() + +2001-06-20 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Patch by Adrian Schroeter <adrian@suse.de>: + - Some QString -> char* conversion fixes + - fixes for autoconf 2.50 + * Use Prune Empty Directories settings not only for updates, + but also for checkouts + +2001-06-19 Richard Moore <rich@kde.org> + + * Ported UI implementation to use XMLGUI + * Made UI more style guide compliant + * Added toolbar configuration + * Ported settings dialog to use KDialogBase + +2001-06-19 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Again a patch by Colin Macleod <colin.macleod@ivata.com> + Compression argument is now configurable per + repository, with a global default. Also, -f + is given to all commands now. + +2001-05-21 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Made compression argument (-z) configurable. + Patch by Colin Macleod <colin.macleod@ivata.com> + +2001-05-17 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed crash when closing settings dialog + * Fixed first line in annotate dialog disappearing + * Remade html documentation from docbook, bringing + it in line with the kde style + * Release 1.4.1 + +2001-05-16 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Cleanup + * Release 1.4 + +2001-05-14 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Patch by Francisco Jose Blasco Abril <francisco.blasco@ds2.es> + Adds to the resolve dialog buttons A and B the additional + choices A+B and B+A. It is also possible to freely edit + items. + * Use -f option also for cvs log + +2001-04-03 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Support for external diff frontends: Based + on a patch by Scott Moore <scott@netcharge.com> + +2001-03-27 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed resolve dialog bug: The merged view was + not scrolled to the relevant position + +2001-03-18 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Copied and cleaned up the << and >> button stuff from resolve + dialog to the diff dialog. Now it is possible to jump between + differences by these buttons. Based on ideas and + code by Francois Biot <francois.biot@sabre.com>. + * Zoom widget in diff dialog improved. Ditto. + * Added a combo in the diff dialog which allows to jump + directly to a difference region + * Release 1.3 + +2001-03-14 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Ignore symbolic links + +2001-03-12 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Hopefully fixed a crash in the listview which + I could not reproduce myself. + +2001-03-03 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Patch by Wynn Wilkes: Allow to open the editor + with multiple files + * Made tab width in diff view customizable + * Fix in doc/Makefile.am for Solaris. + Thanks to Timo Ruottinen <Timo.Ruottinen@iki.fi> + * Some cleanups wrt constness + +2001-03-02 Bernd Gehrmann <bernd@physik.hu-berlin.de> + * Patch by Cosmin Smeu <cosmin@cosmin.com>: + In diff view, replace tabs by spaces + +2001-02-17 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed opening files by double clicking + * Added small icon to makefile + * Show directory items in file tree with folder icon + * Implemented Fold tree + * Implemented Lock files and Unlock files + * Implemented Edit files, Unedit Files, Show editors + * Implemented Hide removed files + * Added label in the status bar which shows + which items are hidden + * Release 1.2 + +2001-02-09 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Changed behaviour of .cvsignore mechanisms + for directories: now directories are ignored, + _except_ if they appear in CVS/Entries. I'm + now quite sure that it matches the behaviour + of CVS itself :-) + * Release 1.1 + +2001-02-08 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * In main view, allow tabbing by keyboard between + file tree and protocol view + * Context menus in main view also by keyboard + +2001-02-07 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Apply ignore rules to files instead of directories + - Yet Another Bug introduced between Betas 2 and 3 + * Ignore CVS directories + * Pressing Return now edits the current file, not one + of the selected files, and never a directory... + * Quote file name in diff dialog + * In Merge dialog, implemented buttons to fetch the + lists of possible tags and branches + +2001-02-06 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Finished filtering in main view - the cast orgy ;-) + +2001-02-04 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Save column sizes of log list + * In main view and history dialog, let the main column + take the remaining space of the list view + * Do not read in contents of directory repeatedly + if it is empty + * Tooltips for menu items + * Options -> Settings, in consistency with other KDE apps + * Rewritten some captions + +2001-02-03 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Implemented Revert - only for cvs 1.11 + +2001-02-01 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * In Update to tag dialog, introduced new option + Update to branch; implemented buttons to fetch the + lists of possible tags and branches + * In protocol view, added popup with items + Clear and Select All + +2001-01-31 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed widgets not appearing in the import dialog + * Made several dialogs non-modal again which I + made modal by accident between Beta 2 and Beta 3 + * Added button for fetching the list of possible tags + in Delete Tag dialog + +2001-01-30 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Updated screenshots in documentation + * Close CVS/Root file after reading + * Don't count diagnostic message from cvs server + as errors when making a patch + * Use configured cvs client when making patch + +2001-01-29 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Layout fix in Add Watch dialog + * Use bigger font in ChangeLog dialog, use + KGlobalSettings::fixedFont() for KDE2 + +2001-01-28 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Put sort column indicators in all list views + * New Bonsai-like blame annotation view + * Reduced free space in log tree, cleaned up the calculation + * Tool tips in log tree contain revision, author and date now + and use Qt's richtext engine; improved positioning + * Session-save configuration (order and sort direction) of log list + * Removed KDE1 related legacy classes + * KDE2 conformant command line handling + * Updated manpage to KDE2 + * Updated libtool/autoconf stuff, should support --prefix now, + removed support for --enable-final and --enable-fast-perl + +2001-01-27 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Removed superfluous line breaks in log tree tool tips + * Fixed bug that prevented cvs info from working when kdehelp + was not installed + * Layout fixes in settings dialog + * Fixed i18n issue in log list + * 1.0beta3 branches from here + +2001-01-23 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Now directories are now never ignored via the various .cvsignore mechanisms + +2001-01-21 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Try to find a nice user name for the change log dialog + automatically + * Don't use entry from change log dialog in commit dialog + if the change log dialog was cancelled + * Made close button in history dialog non-autodefault + +2001-01-20 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Made all dialogs maximizable/minimizable where it makes sense + * Layout fix + * Fixed doc/Makefile.am for non-Linux systems (hopefully :-) + * In the Old Messages combo in the commit dialog, remove + duplicate entries + +2001-01-17 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Patch by Florent Pillet <florent.pillet@opteway.com>: + - In the log dialog, use multi line edits instead of labels + - In the linear log list, additional column with branch + * Korean translation by Yu-Chan, Park <super@susekorea.net> + * Patch by Wynn Wilkes <wynnw@calderasystems.com>: + Making path to the cvs client configurable + * Some cleanup in function argument list order; don't + let dialogs include toplevel.h, instead turned + variable into a function in misc.h... I'm a purist about + this matter ;-) + +2001-01-04 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Always use /bin/sh as shell + * When the progress dialog is canceled, kill the + underlying process instead of destroying the + KProcess object. This avoids a (superfluous) warning + from KProcCtrl + * Removed Ok button from the dialog. + Patch by Guillaume Laurent <glaurent@telegraph-road.org> + * Use KAnimWidget for KDE2 + +2000-12-21 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Release 1.0beta2 + +2000-12-13 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Bug fixes for broken diff dialog in KDE 1 version: + + Do not create modeless dialogs with parent + + Create combo box in commit dialog with minimal size + * Bug fix in log tree: tool tips showed revisions + appearing on several branches with the same root + * Let cvs ignore ~/.cvsrc file when diffing, as that + may lead to conflicting options + +2000-11-24 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Layout improvements in watch dialog + * Implemented 'Create patch' + * More documentation + * Release 1.0beta1 + +2000-11-21 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * In Import dialog, added options for ignoring files + and for binary imports + +2000-11-19 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixes for srcdir != builddir + +2000-11-11 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Made some fonts and the orientation of the main + window splitter configurable + * Fixed log tree for nested branches + +2000-11-10 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * History list can be sorted chronologically + +2000-11-09 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * In Last Change dialog, show newer version on the right + * Session management for commit dialog + * Diff options now customizable + +2000-11-08 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Again a patch by Jan Borsodi <jb@ez.no>: + - In Commit dialog, diffs against repository + can be created by selecting files from the + list box + - List of 30 latest log messages is stored + and available via a combo box + - Wheel mouse support in diff view + - Shortcut F5 for Status + Thanks :-) + +2000-10-27 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Disabled dcop support which doesn't make sense + as long as there's no general convention about + an interface for loading files + +2000-10-17 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Ignore empty lines in output of cvs checkout -c + Patch by Jan Borsodi <jb@ez.no> + * Added /usr/local/include and /usr/local/lib + to autoconf-checked directories + +2000-10-01 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Protect file names with whitespace in them + Based on a patch by Jeff Cody <jcody@logikos.com> + * Allow it to execute a File->Status + automatically when a sandbox is opened. Separate + options for local and remote repositories + +2000-09-19 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed wrong #ifdef statement which disabled + Deselect All and made the selection mechanism + almost unusable with KDE1 + * In diff view, synchronize both horizontal + and vertical scrollbars + * Release 0.7.2 + +2000-09-16 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Added horizontal scrollbars in diff view + * Made number of context lines configurable + * In directory listing, separate directories from files + * Display sticky date tags in a more friendly way + * Adjust columns widths in main view dynamically + * Set focus explicitly in TagDialog, UpdateDialog + and HistoryDialog whenever radio buttons change + * Set initial focus in some dialogs + * Added more accelerators in CheckoutDialog + and WatchDialog + * Fixed recursive removes + * Do no try to read non-existent directories + * More precise coordinate computation on change bar + avoids stripes in certain circumstances + * Fixed highlighted text color + * Show newest revisions first in log message list + * Release 0.7.1 + +2000-09-15 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Avoid -no-rtti because some crap code in + kdelibs crashes without rtti + * Fixed selection by keyboard in log message list + * Avoid exploding status bar width with + long command lines + * Fixed "Deselect all" accelerator + +2000-09-10 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed dist target + * Release 0.7.0 + +2000-09-09 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed spinning gear for KDE2 + * Fixed misinterpretation of mouse events in + log list for KDE2 + +2000-09-08 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Backporting to KDE1. It becomes annoying :-( + * Polished log tree and log list + * Reduced flicker a lot + +2000-09-05 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Implemented change bar in diff frontend + +2000-09-03 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * New diff frontend, based on unified diffs instead + of --side-by-side. + * Keybindings for arrow keys in diff and resolve + dialogs. + * Fixed caption/about data for KDE2 + +2000-08-29 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed Remove behaviour, which may be recursive or not + +2000-07-13 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed problems with latest kdelibs2 and gcc 2.96 + (thanks to Stan Bubrouski) + * Error messages don't confuse the 'Fetch list' + item in the check out dialog any more + * Using QFileDialog instead of completely + broken KFileDialog::getExistingDirectory() + +2000-05-28 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Quote file name when calling an editor + +2000-05-17 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Improvements in the logdialog/log tree by + Florent Pillet <florent.pillet@opteway.com> + * Implemented 'Delete tag' + +2000-05-07 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Implemented Add Watch, Remove watch, + Show watchers + +2000-04-25 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Added option to automatically call 'cvs edit' whenever + a read-only file is edited. Based on a patch by + Steffen Dettmer <steffen@dett.de>. Thanks! + +2000-04-15 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Added documentation for repository access + * Release 0.6.0 + +2000-04-14 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed "Open recent" for Qt 2.x + * Fixed caption for KDE 2.x + +2000-04-11 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Made repositories dialog usable. + +2000-04-09 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * New quoting mechanism which is reliable also when + log messages/file names contain $ or ' + Thanks for the hint, Walter! + +2000-03-09 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Put ok and cancel buttons in the progress dialog + to avoid confusion + +2000-03-06 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * The combo boxes in the log dialog now indicate + that chosing a branch tag selects the branch point + * Repository dialog, unfinished. + +2000-03-05 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Replaced all Done's with Close's + * Moved Add binary from Advanced to File menu + * Enabled tag selectors in log dialog now also for KDE 2 + * Implemented "Open recent" + * Implemented "History" + +2000-03-04 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Added a button in the checkout dialog to obtain + a list of modules + * Improved error message detection + * Show sandbox in caption + +2000-03-02 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Much more detailed pseudo session management. + Please delete your cervisiarc file! + +2000-02-21 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed Checkout and Import which were not working at all + * Check tag names in checkout and import dialog + * Fixed accelerators and added some more + * Implemented "Merge" + * Implemented "Prune empty directories" + * Implemented "Select by tag" in log dialog + * Release 0.5.0 + +2000-02-20 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed bug which caused tooltips for revisions + an branches to disappear in the log tree + * Tags, branches and branchpoints as tooltips in log tree + * Improved log dialog layout + * Implemented "Add binary" + +2000-02-17 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Handle hidden files correctly + * Do not ignore case in files sorting + * Implemented "Last change" + * Release 0.4.0 + +2000-02-15 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Implemented "Update to tag" and "Update to HEAD" + * Implemented "Tag" + +2000-02-14 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Implemented options "Commit recursively" and + "Create directories" + * Ported Checkout/Import dialog to the new scheme + +2000-02-13 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Rewritten the whole UpdateView (well, almost :-). Now it allows + to show and update the revision and tag field. + * Real support for asynchronous operation of Update, Commit, Add + and remove: output goes to new ProtocolView + * Fixed "Unfold Tree" + * Fixed crash caused by too many open files + (.cvsignore files were never closed) + + +2000-02-08 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Ported documentation to docbook, added some + more stuff and adjusted automake system accordingly + +2000-02-06 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Updated autoconf framework to work correctly + with both KDE 1 and KDE 2 + +1999-12-19 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * In Checkout dialog, use $CVSROOT as default repository + (if defined) + * Added command line arguments --help and --version + +1999-11-23 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed bug which caused files mistakenly marked as up to date + * Fixed options menu + * Show wait cursor while child process runs in background + * Made Ok button in settings dialog the default + * Release 0.3.1 + +1999-11-21 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * ChangeLog editor + * Release 0.3.0 + +1999-11-20 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Solution of the startup problem: pseudo-session management + * Simpler handling of command line argument + * Tooltips in LogTree show log message + +1999-10-31 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * DCOP support for KDE HEAD branch + * Settings dialog + * Context menu + * Doubleclick opens file + * 'Open Sandbox' + +1999-09-22 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * KDE2 is really a moving target => more porting: + replaced KQuickHelp by QWhatsThis, + #defined Icon BarIcon + * Made editor configurable + +1999-08-07 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Spinning gear for SubProcDialog and CvsProgressDialog + * Reorganized the whole 'mark updated' code. As + a side effect, there is no more confusion about + bogus 'Up to date' files + * Save/load options + * Implemented 'Unfold tree' + * man page + * Release 0.2.1 + +1999-08-04 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed disappearing revision in log tree view + when branch is longer than trunk + * Changed .kdelnk from Applications -> Development + * Release 0.2 + +1999-08-03 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Implemented recursive update + +1999-08-02 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Changed update, diff and annotate to use + the new CvsProgressDialog + * Preparations for multi log dialog + * Fixed memory leaks in modeless dialogs + which didn't delete themselves on closeEvent() + +1999-08-01 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Implemented CvsProgressDialog which will show error + messages for things like 'update' and allow the user + to interrupt the operation if cvs hangs. + It's _very_ smart :-) + +1999-07-31 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Commit/Add/Remove now show SubProcDialog + to give the user feedback + * Preparations for better error handling: + parseXXX routines return bool now + * Commented out some of the DEBUGOUT. + It was just _too_ much ;-) + +1999-07-20 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Ported to KDE 2 + +1999-07-19 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Implemented "Import" + * Release 0.1 + +1999-07-17 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Another trial in the "Startup in non-CVS directories" + game + * Layout corrections in Checkout dialog + * Mark selections in LogTreeView, + synchronized with LogListView + +1999-07-15 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Gave up on learning docbook. Some documentation + in linuxdoc is now available. + +1999-06-01 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Option -askdir + * Update status field for 'up to date' files + * Release 0.0.2 + +1999-05-25 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Set colors in DiffView explicitly + +1999-05-17 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Fixed 'cvs commit' command + * Preparations for i18n + +1999-05-11 Bernd Gehrmann <bernd@physik.hu-berlin.de> + + * Release 0.0.1 + + + + diff --git a/cervisia/HACKING b/cervisia/HACKING new file mode 100644 index 00000000..dfa61cdd --- /dev/null +++ b/cervisia/HACKING @@ -0,0 +1,79 @@ +Coding Style +============ + +Formatting +---------- + +- No tabs. +- Indent is 4 spaces. +- A line should not exceed 80 chars. +- Brackets are always on separate lines. +- Put spaces between brackets of if, while and + similar statements. + + +Example: + +void MyClass::myFunction(const QString& arg) +{ + if( blah == "halb" ) + { + doSometing(); + } + else + { + varA = varB; + } +} + + + +Header Formatting +----------------- + +- Access modifiers are not indented. +- Double inclusion guard defines are all upper case + letters and are composed of the namespace (if available), + the classname and a H suffix separated by underscores. +- Inside a namespace there is no indentation. + + +Example: + +#ifndef NAMESPACE_MYCLASS_H +#define NAMESPACE_MYCLASS_H + +namespace Namespace +{ + +class MyClass +{ +public: + MyClass(); + +private: + int m_intVar; + KProcess* m_proc; +}; + +} + +#endif + + + +Class and File Names +-------------------- + + + +Class and Variable Names +------------------------ + +- For class, variable and function names separate multiple + words by uppercasing the words preceded by other words. +- Class names start with an uppercase letter. +- Function names start with a lowercase letter. +- Variable names start with a lowercase letter. +- Member Variables of a class start with a 'm_' prefix + followed by an lowercase letter.
\ No newline at end of file diff --git a/cervisia/Makefile.am b/cervisia/Makefile.am new file mode 100644 index 00000000..eb985ea3 --- /dev/null +++ b/cervisia/Makefile.am @@ -0,0 +1,73 @@ +CERVISIA_VERSION = 2.4.10 +INCLUDES = -I./cvsservice -D_BSD_SOURCE $(all_includes) + +SUBDIRS = cvsservice . pics + +bin_PROGRAMS = +lib_LTLIBRARIES = +kdeinit_LTLIBRARIES = cervisia.la +kde_module_LTLIBRARIES = libcervisiapart.la +noinst_LTLIBRARIES = libcervisia.la + +libcervisia_la_SOURCES = annotatedlg.cpp diffdlg.cpp patchoptiondlg.cpp logdlg.cpp \ + progressdlg.cpp progressdlg.skel resolvedlg.cpp resolvedlg_p.cpp annotateview.cpp \ + diffview.cpp loglist.cpp logplainview.cpp logtree.cpp annotatectl.cpp \ + loginfo.cpp misc.cpp qttableview.cpp tooltip.cpp cervisiasettings.kcfgc \ + settingsdlg.cpp settingsdlg_advanced.ui +libcervisia_la_COMPILE_FIRST = cvsservice/cvsservice_stub.h cervisiasettings.h + +libcervisiapart_la_SOURCES = updateview.cpp protocolview.cpp protocolview.skel \ + watchdlg.cpp changelogdlg.cpp historydlg.cpp \ + repositorydlg.cpp commitdlg.cpp checkoutdlg.cpp updatedlg.cpp \ + tagdlg.cpp mergedlg.cpp cvsdir.cpp repositories.cpp cervisiapart.cpp \ + addrepositorydlg.cpp addremovedlg.cpp watchersdlg.cpp \ + updateview_items.cpp updateview_visitors.cpp entry.cpp \ + entry_status.cpp stringmatcher.cpp cvsinitdlg.cpp ignorelistbase.cpp dirignorelist.cpp \ + globalignorelist.cpp editwithmenu.cpp logmessageedit.cpp +libcervisiapart_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) +libcervisiapart_la_LIBADD = $(LIB_KFILE) $(LIB_KPARTS) $(LIB_KUTILS) \ + cvsservice/libcvsservice.la libcervisia.la +libcervisiapart_la_COMPILE_FIRST = cvsservice/cvsservice_stub.h cervisiasettings.h + +cervisia_la_SOURCES = main.cpp cervisiashell.cpp +cervisia_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) -module $(KDE_PLUGIN) +cervisia_la_LIBADD = $(LIB_KPARTS) $(LIB_KUTILS) cvsservice/libcvsservice.la libcervisia.la +cervisia_la_COMPILE_FIRST = cvsservice/cvsservice_stub.h cervisiasettings.h + +man_MANS = cervisia.1 + +METASOURCES = AUTO +KDE_ICON = AUTO + +EXTRA_DIST = cervisia.desktop cervisia.png cervisia-small.png +CLEANFILES = cervisia.1 + +xdg_apps_DATA = cervisia.desktop + +kde_kcfg_DATA = cervisiapart.kcfg + +partrcdir = $(kde_datadir)/cervisiapart +partrc_DATA = cervisiaui.rc + +shellrcdir = $(kde_datadir)/cervisia +shellrc_DATA = cervisiashellui.rc eventsrc + +update_DATA = cervisia.upd +update_SCRIPTS = move_repositories.pl change_colors.pl cervisia-normalize_cvsroot.pl cervisia-change_repos_list.pl +updatedir = $(kde_datadir)/kconf_update + +messages: rc.cpp + $(EXTRACTRC) *.rc >> rc.cpp + $(XGETTEXT) -C *.cpp *.h -o $(podir)/cervisia.pot + +srcdoc: + $(kde_bindir)/kdoc -a -p -d classdocs -n 'Cervisia' *.h -lqt -lkdecore -lkdeui -lkparts + +cervisia.1: $(srcdir)/cervisia.1.in + sed -e 's%_KDEHTMLDIR_%'${kde_htmldir}'%g;' \ + -e 's%_KDECONFDIR_%'${kde_confdir}'%g;' \ + < $(srcdir)/cervisia.1.in > cervisia.1 + +cervisia.1.in: cervisia.pod + pod2man --center "Cervisia" --release "${CERVISIA_VERSION}" \ + cervisia.pod > cervisia.1.in diff --git a/cervisia/README b/cervisia/README new file mode 100644 index 00000000..e310034e --- /dev/null +++ b/cervisia/README @@ -0,0 +1,19 @@ +Cervisia requires cvs 1.10 on the cvs server. With +cvs 1.9, diffs don't work! As this old version has a +y2k bug, switching is advisable anyway. + +From version 1.1 on, Cervisia implements the menu item +File->Revert. This hands over the command 'cvs update -C' +to the cvs client. This works only for cvs >= 1.11. + +Bug reports are welcome. My time is limited, so don't +expect wonders. +If you want to contribute bugfixes or improvements, +check out the source code from anonymous kde cvs. +In order to bootstrap the package, you have to +'make -f Makefile.cvs' there. Then you can make patches +either by using 'cvs diff -u' or by using the 'Create +patch' menu item in Cervisia. + + +Christian Loose <christian.loose@hamburg.de> diff --git a/cervisia/TODO b/cervisia/TODO new file mode 100644 index 00000000..f31cb683 --- /dev/null +++ b/cervisia/TODO @@ -0,0 +1,26 @@ +Ideas in no special order + +* Per-repository settings like enabling of + watch/edit/lock features + +* Log dialog option: Show only tagged revisions + +* Log dialog option: Show only your revisions + +* Log dialog: Allow to select latest on branch + +* Multi Log view + +* Implement "Add to .cvsignore" + +* cvs init + +* Implement a nice workaround for $CVSROOT/CVSROOT/cvsignore + +* Maybe use ui files for dialogs + +* Watch open working copy directory for changes (KDirWatcher) + +* Automatic 'cvs -n update' in specified time intervals (QTimer) + +* Add commit message to ChangeLog file (checkbox in commit dialog) diff --git a/cervisia/addremovedlg.cpp b/cervisia/addremovedlg.cpp new file mode 100644 index 00000000..108ac80e --- /dev/null +++ b/cervisia/addremovedlg.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "addremovedlg.h" + +#include <qfileinfo.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qlistbox.h> +#include <qstringlist.h> +#include <kapplication.h> +#include <kiconloader.h> +#include <klocale.h> + + +AddRemoveDialog::AddRemoveDialog(ActionType action, QWidget* parent, const char* name) + : KDialogBase(parent, name, true, QString::null, + Ok | Cancel | Help, Ok, true) +{ + setCaption( (action==Add)? i18n("CVS Add") : + (action==AddBinary)? i18n("CVS Add Binary") : + i18n("CVS Remove") ); + + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout *layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + QLabel *textlabel = new QLabel + ( (action==Add)? i18n("Add the following files to the repository:") : + (action==AddBinary)? i18n("Add the following binary files to the repository:") : + i18n("Remove the following files from the repository:") , + mainWidget ); + layout->addWidget(textlabel); + + m_listBox = new QListBox(mainWidget); + m_listBox->setSelectionMode(QListBox::NoSelection); + layout->addWidget(m_listBox, 5); + + // Add warning message to dialog when user wants to remove a file + if (action==Remove) + { + QBoxLayout *warningLayout = new QHBoxLayout; + + QLabel *warningIcon = new QLabel(mainWidget); + KIconLoader *loader = kapp->iconLoader(); + warningIcon->setPixmap(loader->loadIcon("messagebox_warning", KIcon::NoGroup, + KIcon::SizeMedium, KIcon::DefaultState, + 0, true)); + warningLayout->addWidget(warningIcon); + + QLabel *warningText = new QLabel(i18n("This will also remove the files from " + "your local working copy."), mainWidget); + warningLayout->addWidget(warningText); + + layout->addSpacing(5); + layout->addLayout(warningLayout); + layout->addSpacing(5); + } + + if( action == Remove ) + setHelp("removingfiles"); + else + setHelp("addingfiles"); +} + + +void AddRemoveDialog::setFileList(const QStringList& files) +{ + // the dot for the root directory is hard to see, so + // we convert it to the absolut path + if( files.find(".") != files.end() ) + { + QStringList copy(files); + int idx = copy.findIndex("."); + copy[idx] = QFileInfo(".").absFilePath(); + + m_listBox->insertStringList(copy); + } + else + m_listBox->insertStringList(files); +} + + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/cervisia/addremovedlg.h b/cervisia/addremovedlg.h new file mode 100644 index 00000000..06b98260 --- /dev/null +++ b/cervisia/addremovedlg.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef ADDREMOVEDLG_H +#define ADDREMOVEDLG_H + + +#include <kdialogbase.h> + + +class QListBox; +class QStringList; + + +class AddRemoveDialog : public KDialogBase +{ +public: + enum ActionType { Add, AddBinary, Remove }; + + explicit AddRemoveDialog(ActionType action, QWidget* parent=0, const char* name=0); + + void setFileList(const QStringList& files); + +private: + QListBox* m_listBox; +}; + +#endif + + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/cervisia/addrepositorydlg.cpp b/cervisia/addrepositorydlg.cpp new file mode 100644 index 00000000..d1d24cb0 --- /dev/null +++ b/cervisia/addrepositorydlg.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "addrepositorydlg.h" + +#include <qcheckbox.h> +#include <qhbox.h> +#include <qlabel.h> +#include <qlayout.h> + +#include <kconfig.h> +#include <klineedit.h> +#include <klocale.h> +#include <knuminput.h> + + +AddRepositoryDialog::AddRepositoryDialog(KConfig& cfg, const QString& repo, + QWidget* parent, const char* name) + : KDialogBase(parent, name, true, i18n("Add Repository"), + Ok | Cancel, Ok, true) + , partConfig(cfg) +{ + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout* layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + QLabel* repo_label = new QLabel(i18n("&Repository:"), mainWidget); + layout->addWidget(repo_label); + + repo_edit = new KLineEdit(mainWidget); + repo_edit->setFocus(); + repo_label->setBuddy(repo_edit); + if( !repo.isNull() ) + { + repo_edit->setText(repo); + repo_edit->setEnabled(false); + } + layout->addWidget(repo_edit); + + QLabel* rsh_label = new QLabel(i18n("Use remote &shell (only for :ext: repositories):"), mainWidget); + layout->addWidget(rsh_label); + + rsh_edit = new KLineEdit(mainWidget); + rsh_label->setBuddy(rsh_edit); + layout->addWidget(rsh_edit); + + QLabel* server_label = new QLabel(i18n("Invoke this program on the server side:"), + mainWidget); + layout->addWidget(server_label); + + server_edit = new KLineEdit(mainWidget); + server_label->setBuddy(server_edit); + layout->addWidget(server_edit); + + QHBox* compressionBox = new QHBox(mainWidget); + m_useDifferentCompression = new QCheckBox(i18n("Use different &compression level:"), compressionBox); + + m_compressionLevel = new KIntNumInput(compressionBox); + m_compressionLevel->setRange(0, 9, 1, false); + layout->addWidget(compressionBox); + + m_retrieveCvsignoreFile = new QCheckBox(i18n("Download cvsignore file from " + "server"), mainWidget); + layout->addWidget(m_retrieveCvsignoreFile); + + connect( repo_edit, SIGNAL(textChanged(const QString&)), + this, SLOT(repoChanged()) ); + connect( m_useDifferentCompression, SIGNAL(toggled(bool)), + this, SLOT(compressionToggled(bool)) ); + repoChanged(); + + QSize size = configDialogSize(partConfig, "AddRepositoryDialog"); + resize(size); +} + + +AddRepositoryDialog::~AddRepositoryDialog() +{ + saveDialogSize(partConfig, "AddRepositoryDialog"); +} + + +void AddRepositoryDialog::setRsh(const QString& rsh) +{ + rsh_edit->setText(rsh); +} + + +void AddRepositoryDialog::setServer(const QString& server) +{ + server_edit->setText(server); +} + + +void AddRepositoryDialog::setCompression(int compression) +{ + if( compression < 0 ) + { + // TODO: use KConfigXT to retrieve default compression level + m_compressionLevel->setValue(0); + m_useDifferentCompression->setChecked(false); + } + else + { + m_useDifferentCompression->setChecked(true); + m_compressionLevel->setValue(compression); + } + + compressionToggled(m_useDifferentCompression->isChecked()); +} + + +void AddRepositoryDialog::setRetrieveCvsignoreFile(bool enabled) +{ + m_retrieveCvsignoreFile->setChecked(enabled); +} + + +QString AddRepositoryDialog::repository() const +{ + return repo_edit->text(); +} + + +QString AddRepositoryDialog::rsh() const +{ + return rsh_edit->text(); +} + + +QString AddRepositoryDialog::server() const +{ + return server_edit->text(); +} + + +int AddRepositoryDialog::compression() const +{ + if( m_useDifferentCompression->isChecked() ) + return m_compressionLevel->value(); + else + return -1; +} + + +bool AddRepositoryDialog::retrieveCvsignoreFile() const +{ + return m_retrieveCvsignoreFile->isChecked(); +} + + +void AddRepositoryDialog::setRepository(const QString& repo) +{ + setCaption(i18n("Repository Settings")); + + repo_edit->setText(repo); + repo_edit->setEnabled(false); +} + + +void AddRepositoryDialog::repoChanged() +{ + QString repo = repository(); + rsh_edit->setEnabled((!repo.startsWith(":pserver:")) + && repo.contains(":")); + m_useDifferentCompression->setEnabled(repo.contains(":")); + if( !repo.contains(":") ) + m_compressionLevel->setEnabled(false); + else + compressionToggled(m_useDifferentCompression->isChecked()); +} + + +void AddRepositoryDialog::compressionToggled(bool checked) +{ + m_compressionLevel->setEnabled(checked); +} + +#include "addrepositorydlg.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/addrepositorydlg.h b/cervisia/addrepositorydlg.h new file mode 100644 index 00000000..8fbf66fd --- /dev/null +++ b/cervisia/addrepositorydlg.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef ADDREPOSITORYDLG_H +#define ADDREPOSITORYDLG_H + +#include <kdialogbase.h> + +class QCheckBox; +class KConfig; +class KIntNumInput; +class KLineEdit; + + +class AddRepositoryDialog : public KDialogBase +{ + Q_OBJECT + +public: + AddRepositoryDialog(KConfig& cfg, const QString& repo, QWidget* parent = 0, + const char* name = 0); + virtual ~AddRepositoryDialog(); + + void setRepository(const QString& repo); + void setRsh(const QString& rsh); + void setServer(const QString& server); + void setCompression(int compression); + void setRetrieveCvsignoreFile(bool enabled); + + QString repository() const; + QString rsh() const; + QString server() const; + int compression() const; + bool retrieveCvsignoreFile() const; + +private slots: + void repoChanged(); + void compressionToggled(bool checked); + +private: + KLineEdit* repo_edit; + KLineEdit* rsh_edit; + KLineEdit* server_edit; + QCheckBox* m_useDifferentCompression; + QCheckBox* m_retrieveCvsignoreFile; + KIntNumInput* m_compressionLevel; + KConfig& partConfig; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/annotatectl.cpp b/cervisia/annotatectl.cpp new file mode 100644 index 00000000..31f95f84 --- /dev/null +++ b/cervisia/annotatectl.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2002-2003 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "annotatectl.h" + +#include <qdatetime.h> +#include <qmap.h> + +#include <dcopref.h> +#include <klocale.h> +#include <krfcdate.h> + +#include "annotatedlg.h" +#include "loginfo.h" +#include "progressdlg.h" +#include "cvsservice_stub.h" +#include "cvsjob_stub.h" + +using namespace Cervisia; + +struct AnnotateController::Private +{ + typedef QMap<QString, QString> RevisionCommentMap; + RevisionCommentMap comments; // maps comment to a revision + + CvsService_stub* cvsService; + AnnotateDialog* dialog; + ProgressDialog* progress; + + bool execute(const QString& fileName, const QString& revision); + void parseCvsLogOutput(); + void parseCvsAnnotateOutput(); +}; + + +AnnotateController::AnnotateController(AnnotateDialog* dialog, CvsService_stub* cvsService) + : d(new Private) +{ + // initialize private data + d->cvsService = cvsService; + d->dialog = dialog; + d->progress = 0; +} + + +AnnotateController::~AnnotateController() +{ + delete d; +} + + +void AnnotateController::showDialog(const QString& fileName, const QString& revision) +{ + if( !d->execute(fileName, revision) ) + { + delete d->dialog; + return; + } + + d->parseCvsLogOutput(); + d->parseCvsAnnotateOutput(); + + // hide progress dialog + delete d->progress; d->progress = 0; + + d->dialog->setCaption(i18n("CVS Annotate: %1").arg(fileName)); + d->dialog->show(); +} + + +bool AnnotateController::Private::execute(const QString& fileName, const QString& revision) +{ + DCOPRef job = cvsService->annotate(fileName, revision); + if( !cvsService->ok() ) + return false; + + progress = new ProgressDialog(dialog, "Annotate", job, "annotate", i18n("CVS Annotate")); + + return progress->execute(); +} + + +void AnnotateController::Private::parseCvsLogOutput() +{ + QString line, comment, rev; + + enum { Begin, Tags, Admin, Revision, + Author, Branches, Comment, Finished } state; + + state = Begin; + while( progress->getLine(line) ) + { + switch( state ) + { + case Begin: + if( line == "symbolic names:" ) + state = Tags; + break; + case Tags: + if( line[0] != '\t' ) + state = Admin; + break; + case Admin: + if( line == "----------------------------" ) + state = Revision; + break; + case Revision: + rev = line.section(' ', 1, 1); + state = Author; + break; + case Author: + state = Branches; + break; + case Branches: + if( !line.startsWith("branches:") ) + { + state = Comment; + comment = line; + } + break; + case Comment: + if( line == "----------------------------" ) + state = Revision; + else if( line == "=============================================================================" ) + state = Finished; + if( state == Comment ) + comment += QString("\n") + line; + else + comments[rev] = comment; + break; + case Finished: + ; + } + + if (state == Finished) + break; + } + + // skip header part of cvs annotate output + bool notEof = true; + while( notEof && !line.startsWith("*****") ) + notEof = progress->getLine(line); +} + + +void AnnotateController::Private::parseCvsAnnotateOutput() +{ + LogInfo logInfo; + QString rev, content, line; + QString oldRevision = ""; + bool odd = false; + + while( progress->getLine(line) ) + { + QString dateString = line.mid(23, 9); + if( !dateString.isEmpty() ) + logInfo.m_dateTime.setTime_t(KRFCDate::parseDate(dateString), Qt::UTC); + + rev = line.left(13).stripWhiteSpace(); + logInfo.m_author = line.mid(14, 8).stripWhiteSpace(); + content = line.mid(35, line.length()-35); + + logInfo.m_comment = comments[rev]; + if( logInfo.m_comment.isNull() ) + logInfo.m_comment = ""; + + if( rev == oldRevision ) + { + logInfo.m_author = QString::null; + rev = QString::null; + } + else + { + oldRevision = rev; + odd = !odd; + } + + logInfo.m_revision = rev; + + dialog->addLine(logInfo, content, odd); + } +} diff --git a/cervisia/annotatectl.h b/cervisia/annotatectl.h new file mode 100644 index 00000000..d6b76437 --- /dev/null +++ b/cervisia/annotatectl.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2002 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef ANNOTATECTL_H +#define ANNOTATECTL_H + +#include <qstring.h> + +class AnnotateDialog; +class CvsService_stub; +class QWidget; + + +class AnnotateController +{ +public: + AnnotateController(AnnotateDialog* dialog, CvsService_stub* cvsService); + ~AnnotateController(); + + void showDialog(const QString& fileName, const QString& revision = QString::null); + +private: + struct Private; + Private* d; +}; + + +#endif diff --git a/cervisia/annotatedlg.cpp b/cervisia/annotatedlg.cpp new file mode 100644 index 00000000..40b0d555 --- /dev/null +++ b/cervisia/annotatedlg.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "annotatedlg.h" + +#include "annotateview.h" + + +AnnotateDialog::AnnotateDialog(KConfig& cfg, QWidget *parent, const char *name) + : KDialogBase(parent, name, false, QString::null, + Close | Help, Close, true) + , partConfig(cfg) +{ + annotate = new AnnotateView(partConfig, this); + setMainWidget(annotate); + + setHelp("annotate"); + + setWFlags(Qt::WDestructiveClose | getWFlags()); + + QSize size = configDialogSize(partConfig, "AnnotateDialog"); + resize(size); +} + + +AnnotateDialog::~AnnotateDialog() +{ + saveDialogSize(partConfig, "AnnotateDialog"); +} + + +void AnnotateDialog::addLine(const Cervisia::LogInfo& logInfo, + const QString& content, bool odd) +{ + annotate->addLine(logInfo, content, odd); +} + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/annotatedlg.h b/cervisia/annotatedlg.h new file mode 100644 index 00000000..48299cf3 --- /dev/null +++ b/cervisia/annotatedlg.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef ANNOTATEDLG_H +#define ANNOTATEDLG_H + + +#include <kdialogbase.h> + + +class AnnotateView; +class QDate; +class KConfig; + +namespace Cervisia +{ +struct LogInfo; +} + + +class AnnotateDialog : public KDialogBase +{ +public: + + explicit AnnotateDialog( KConfig& cfg, QWidget *parent=0, const char *name=0 ); + + virtual ~AnnotateDialog(); + + void addLine(const Cervisia::LogInfo& logInfo, const QString& content, + bool odd); + +private: + AnnotateView *annotate; + KConfig& partConfig; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/annotateview.cpp b/cervisia/annotateview.cpp new file mode 100644 index 00000000..594ca936 --- /dev/null +++ b/cervisia/annotateview.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann <bernd@mail.berlios.de> + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "annotateview.h" + +#include <qheader.h> +#include <qpainter.h> +#include <kconfig.h> +#include <kglobalsettings.h> + +#include "loginfo.h" +#include "tooltip.h" + + +using namespace Cervisia; + + +class AnnotateViewItem : public QListViewItem +{ +public: + enum { LineNumberColumn, AuthorColumn, ContentColumn }; + + AnnotateViewItem(AnnotateView *parent, const LogInfo& logInfo, + const QString &content, bool odd, int linenumber); + + virtual int compare(QListViewItem *item, int col, bool ascending) const; + virtual int width(const QFontMetrics &, const QListView *, int col) const; + virtual QString text(int col) const; + virtual void paintCell(QPainter *, const QColorGroup &, int, int, int); + +private: + LogInfo m_logInfo; + QString m_content; + bool m_odd; + int m_lineNumber; + friend class AnnotateView; + + static const int BORDER; +}; + + +const int AnnotateViewItem::BORDER = 4; + + +AnnotateViewItem::AnnotateViewItem(AnnotateView *parent, const LogInfo& logInfo, + const QString &content, bool odd, int linenumber) + : QListViewItem(parent) + , m_logInfo(logInfo) + , m_content(content) + , m_odd(odd) + , m_lineNumber(linenumber) +{} + + +int AnnotateViewItem::compare(QListViewItem *item, int, bool) const +{ + int linenum1 = m_lineNumber; + int linenum2 = static_cast<AnnotateViewItem*>(item)->m_lineNumber; + + return (linenum2 > linenum1)? -1 : (linenum2 < linenum1)? 1 : 0; +} + + +QString AnnotateViewItem::text(int col) const +{ + switch (col) + { + case LineNumberColumn: + return QString::number(m_lineNumber); + case AuthorColumn: + if( m_logInfo.m_author.isNull() ) + return QString::null; + else + return (m_logInfo.m_author + QChar(' ') + m_logInfo.m_revision); + case ContentColumn: + return m_content; + default: + ; + }; + + return QString::null; +} + + +void AnnotateViewItem::paintCell(QPainter *p, const QColorGroup &, int col, int width, int align) +{ + QColor backgroundColor; + + switch (col) + { + case LineNumberColumn: + backgroundColor = KGlobalSettings::highlightColor(); + p->setPen(KGlobalSettings::highlightedTextColor()); + break; + default: + backgroundColor = m_odd ? KGlobalSettings::baseColor() + : KGlobalSettings::alternateBackgroundColor(); + p->setPen(KGlobalSettings::textColor()); + break; + }; + + p->fillRect(0, 0, width, height(), backgroundColor); + + QString str = text(col); + if (str.isEmpty()) + return; + + if (align & (AlignTop || AlignBottom) == 0) + align |= AlignVCenter; + + p->drawText(BORDER, 0, width - 2*BORDER, height(), align, str); +} + + + +int AnnotateViewItem::width(const QFontMetrics &fm, const QListView *, int col) const +{ + return fm.width(text(col)) + 2*BORDER; +} + + +/*! + @todo The dummy column (remaining space eater) doesn't work + caused by a bug in QHeader::adjustHeaderSize() in Qt <= 3.0.4. +*/ + +AnnotateView::AnnotateView(KConfig &cfg, QWidget *parent, const char *name) + : QListView(parent, name, WRepaintNoErase | WResizeNoErase) +{ + setFrameStyle(QFrame::WinPanel | QFrame::Sunken); + setAllColumnsShowFocus(true); + setShowToolTips(false); + setSelectionMode(NoSelection); + header()->hide(); + // setResizeMode(LastColumn); + + addColumn(QString::null); + addColumn(QString::null); + addColumn(QString::null); + + setSorting(AnnotateViewItem::LineNumberColumn); + setColumnAlignment(AnnotateViewItem::LineNumberColumn, Qt::AlignRight); + + ToolTip* toolTip = new ToolTip(viewport()); + + connect(toolTip, SIGNAL(queryToolTip(const QPoint&, QRect&, QString&)), + this, SLOT(slotQueryToolTip(const QPoint&, QRect&, QString&))); + + KConfigGroupSaver cs(&cfg, "LookAndFeel"); + setFont(cfg.readFontEntry("AnnotateFont")); +} + + + +void AnnotateView::addLine(const LogInfo& logInfo, const QString& content, + bool odd) +{ + new AnnotateViewItem(this, logInfo, content, odd, childCount()+1); +} + + +QSize AnnotateView::sizeHint() const +{ + QFontMetrics fm(fontMetrics()); + return QSize(100 * fm.width("0"), 10 * fm.lineSpacing()); +} + + +void AnnotateView::slotQueryToolTip(const QPoint& viewportPos, + QRect& viewportRect, + QString& text) +{ + if (const AnnotateViewItem* item = static_cast<AnnotateViewItem*>(itemAt(viewportPos))) + { + const int column(header()->sectionAt(viewportPos.x())); + if ((column == AnnotateViewItem::AuthorColumn) && !item->m_logInfo.m_author.isNull()) + { + viewportRect = itemRect(item); + text = item->m_logInfo.createToolTipText(false); + } + } +} + + +#include "annotateview.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/annotateview.h b/cervisia/annotateview.h new file mode 100644 index 00000000..be74d430 --- /dev/null +++ b/cervisia/annotateview.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann <bernd@mail.berlios.de> + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef ANNOTATEVIEW_H +#define ANNOTATEVIEW_H + + +#include <qlistview.h> + + +class KConfig; + + +namespace Cervisia +{ +struct LogInfo; +} + + +class AnnotateView : public QListView +{ + Q_OBJECT + +public: + + explicit AnnotateView( KConfig &cfg, QWidget *parent=0, const char *name=0 ); + + void addLine(const Cervisia::LogInfo& logInfo, const QString& content, + bool odd); + + virtual QSize sizeHint() const; + +private slots: + + void slotQueryToolTip(const QPoint&, QRect&, QString&); +}; + + +#endif diff --git a/cervisia/cervisia-change_repos_list.pl b/cervisia/cervisia-change_repos_list.pl new file mode 100644 index 00000000..925c49ee --- /dev/null +++ b/cervisia/cervisia-change_repos_list.pl @@ -0,0 +1,59 @@ +#!/usr/bin/perl + +# (copied from kdesdk/cervisia/misc.cpp) +# These regular expression parts aren't useful to check the validity of the +# CVSROOT specification. They are just used to extract the different parts of it. +$usernamerx = "([a-z0-9_][a-z0-9_-]*)?"; +$passwordrx = "(:[^@]+)?"; +$hostrx = "([^:/]+)"; +$portrx = "(:(\\d*))?"; +$pathrx = "(/.*)"; + +# concat above regexps into a single expression +$regexp = join('', ":pserver:(", $usernamerx, $passwordrx, "@)?", $hostrx, $portrx, $pathrx); + +$loginuser = getlogin || getpwuid($<); + +while(<>) +{ + ($key) = ($_ =~ /([^=]*)=(.*)$/); + ($value) = ($_ =~ /^[^=]*=(.*)$/); + + if( $key eq "Repos" ) + { + @repos = split(',', $value); + + foreach $repo ( @repos ) + { + # pserver CVSROOT specification? + if( $repo =~ m/($regexp)/ ) + { + # extract username, hostname, port and path from CVSROOT + $username = $3; + $hostname = $5; + $port = $7; + $path = $8; + + # replace empty port number + $port =~ s/^$/2401/; + + # replace empty username + $username =~ s/^$/$loginuser/; + + # create normalized CVSROOT specification + $repo = join('', ":pserver:", $username, "@", $hostname, ":", $port, $path); + } + } + + # remove duplicates from array + %seen = (); + @repos = grep { ! $seen{$_} ++ } @repos; + + $value = join(',', @repos); + print "# DELETE " . $key . "\n"; + print $key . "=" . $value . "\n"; + next; + } + + print $_; +} diff --git a/cervisia/cervisia-normalize_cvsroot.pl b/cervisia/cervisia-normalize_cvsroot.pl new file mode 100644 index 00000000..8a96dcb8 --- /dev/null +++ b/cervisia/cervisia-normalize_cvsroot.pl @@ -0,0 +1,53 @@ +#!/usr/bin/perl + +# (copied from kdesdk/cervisia/misc.cpp) +# These regular expression parts aren't useful to check the validity of the +# CVSROOT specification. They are just used to extract the different parts of it. +$usernamerx = "([a-z0-9_][a-z0-9_-]*)?"; +$passwordrx = "(:[^@]+)?"; +$hostrx = "([^:/]+)"; +$portrx = "(:(\\d*))?"; +$pathrx = "(/.*)"; + +# concat above regexps into a single expression +$regexp = join('', ":pserver:(", $usernamerx, $passwordrx, "@)?", $hostrx, $portrx, $pathrx); + +$loginuser = getlogin || getpwuid($<); + +while(<>) +{ + # skip empty lines + next if /^$/; + + # config group for a repository? + if( /^\[Repository-(.+)\]$/ ) + { + $oldcvsroot = $1; + + # pserver CVSROOT specification? + if( $oldcvsroot =~ m/($regexp)/ ) + { + # extract username, hostname, port and path from CVSROOT + $username = $3; + $hostname = $5; + $port = $7; + $path = $8; + + # replace empty port number + $port =~ s/^$/2401/; + + # replace empty username + $username =~ s/^$/$loginuser/; + + # create normalized CVSROOT specification + $newcvsroot = join('', ":pserver:", $username, "@", $hostname, ":", $port, $path); + + print "# DELETEGROUP [Repository-$oldcvsroot]\n"; + print "[Repository-$newcvsroot]\n"; + } + + next; + } + + print $_; +} diff --git a/cervisia/cervisia.1.in b/cervisia/cervisia.1.in new file mode 100644 index 00000000..b810f98c --- /dev/null +++ b/cervisia/cervisia.1.in @@ -0,0 +1,217 @@ +.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.3 +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sh \" Subsection heading +.br +.if t .Sp +.ne 5 +.PP +\fB\\$1\fR +.PP +.. +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" Set up some character translations and predefined strings. \*(-- will +.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left +.\" double quote, and \*(R" will give a right double quote. | will give a +.\" real vertical bar. \*(C+ will give a nicer C++. Capital omega is used to +.\" do unbreakable dashes and therefore won't be available. \*(C` and \*(C' +.\" expand to `' in nroff, nothing in troff, for use with C<>. +.tr \(*W-|\(bv\*(Tr +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.ie n \{\ +. ds -- \(*W- +. ds PI pi +. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +. ds L" "" +. ds R" "" +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds -- \|\(em\| +. ds PI \(*p +. ds L" `` +. ds R" '' +'br\} +.\" +.\" If the F register is turned on, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. nr % 0 +. rr F +.\} +.\" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.hy 0 +.if n .na +.\" +.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). +.\" Fear. Run. Save yourself. No user-serviceable parts. +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds / +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +.\} +.rm #[ #] #H #V #F C +.\" ======================================================================== +.\" +.IX Title "CERVISIA 1" +.TH CERVISIA 1 "2006-01-05" "2.4.0" "Cervisia" +.SH "NAME" +Cervisia \- Graphical CVS frontend +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +\&\fBcervisia\fR + [\ \fB\-\-display\fR\ \fIdisplay\fR\ ] + [\ \fB\-\-caption\fR\ \fIcaption\fR\ ] + [\ \fB\-\-icon\fR\ \fIicon\fR\ ] + [\ \fB\-\-miniicon\fR\ \fIminiicon\fR\ ] + [\ \fB\-\-config\fR\ \fIfilename\fR\ ] + [\ \fB\-\-dcopserver\fR\ \fIserver\fR\ ] + [\ \fB\-\-nocrashhandler\fR\ ] + [\ \fB\-\-waitforwm\fR\ ] + [\ \fB\-\-style\fR\ \fIstyle\fR\ ] + [\ \fB\-\-geometry\fR\ \fIgeometry\fR\ ] + [\ \fB\-\-resolve\fR\ \fIfilename\fR\ ] + [\ \fB\-\-log\fR\ \fIfilename\fR\ ] + [\ \fB\-\-annotate\fR\ \fIfilename\fR\ ] + [\ \fIdirectory\fR\ ] +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +Cervisia is a graphical user interface for the Concurrent Versions +System. It is based on the \s-1KDE\s0 libraries and therefore shares +their Look'n'Feel, configuration and help system. +.SH "OPTIONS" +.IX Header "OPTIONS" +Cervisia accepts the following options: +.IP "\fIdirectory\fR" 4 +.IX Item "directory" +Tells Cervisia to open the sandbox in \fIdirectory\fR at startup +.IP "\fB\-\-resolve\fR \fIfilename\fR" 4 +.IX Item "--resolve filename" +Shows a resolve dialog for the given file +.IP "\fB\-\-log\fR \fIfilename\fR" 4 +.IX Item "--log filename" +Shows a log dialog for the given file +.IP "\fB\-\-annotate\fR \fIfilename\fR" 4 +.IX Item "--annotate filename" +Shows a annotation dialog for the given file +.IP "\fB\-\-caption\fR \fIcaption\fR" 4 +.IX Item "--caption caption" +Sets the caption, i. e. what is shown in the title bar +.IP "\fB\-\-icon\fR \fIicon\fR" 4 +.IX Item "--icon icon" +Sets the program's icon (used by window managers and panels) +.IP "\fB\-\-miniicon\fR \fIminiicon\fR" 4 +.IX Item "--miniicon miniicon" +Sets the program's mini icon (used by window managers and panels) +.IP "\fB\-\-config\fR \fIfilename\fR" 4 +.IX Item "--config filename" +Uses the given file for the configuration +.IP "\fB\-\-dcopserver\fR \fIserver\fR" 4 +.IX Item "--dcopserver server" +Sets the dcopserver the program should use +.IP "\fB\-\-nocrashhandler\fR" 4 +.IX Item "--nocrashhandler" +Disables the crash handler. Use this to get core dumps +.IP "\fB\-\-waitforwm\fR" 4 +.IX Item "--waitforwm" +Waits for a \s-1WM_NET\s0 compatible windowmanager +.IP "\fB\-\-style\fR \fIstyle\fR" 4 +.IX Item "--style style" +Sets the application \s-1GUI\s0 style +.IP "\fB\-\-geometry\fR \fIgeometry\fR" 4 +.IX Item "--geometry geometry" +Sets the geometry of the main window +.SH "FILES" +.IX Header "FILES" +\&\fI_KDECONFDIR_/cervisiarc\fR \- global configuration file +.Sp +\&\fI$HOME/.kde/share/config/cervisiarc\fR \- user-specific configuration file +.SH "SEE ALSO" +.IX Header "SEE ALSO" +\&\fI_KDEHTMLDIR_/en/cervisia/index.html\fR +.Sp +\&\fIcvs\fR\|(1) +.Sp +\&\fIhttp://cervisia.kde.org/\fR +.SH "AUTHOR" +.IX Header "AUTHOR" +Cervisia was originally developed by Bernd Gehrmann and is now maintained by +Christian Loose <christian.loose@kdemail.net>. +.SH "REPORTING BUGS" +.IX Header "REPORTING BUGS" +Report bugs at http://bugs.kde.org. diff --git a/cervisia/cervisia.desktop b/cervisia/cervisia.desktop new file mode 100644 index 00000000..b8745e0f --- /dev/null +++ b/cervisia/cervisia.desktop @@ -0,0 +1,74 @@ +[Desktop Entry] +GenericName=CVS Frontend +GenericName[af]=Cvs Voorprogram +GenericName[az]=CVS Ara Üzü +GenericName[bg]=Програма за CVS +GenericName[ca]=Programa de CVS +GenericName[cs]=Rozhraní pro CVS +GenericName[cy]=Ochr Blaen CVS +GenericName[da]=CVS-grænseflade +GenericName[de]=Graphische Oberfläche für CVS +GenericName[eo]=Fasado por la versioadministrilo "CVS" +GenericName[es]=Interfaz CVS +GenericName[et]=CVSi kasutajaliides +GenericName[eu]=CVS interfazea +GenericName[fa]=پایانۀ CVS +GenericName[fi]=Käyttöliittymä CVS:lle +GenericName[fr]=Interface graphique pour CVS +GenericName[ga]=Comhéadan ar CVS +GenericName[gl]=Interface para CVS +GenericName[he]=ממשק CVS +GenericName[hi]=सीवीएस फ्रन्टएण्ड +GenericName[hr]=CVS sučelje +GenericName[hu]=CVS-kliens +GenericName[is]=Myndrænt viðmót á CVS +GenericName[it]=Interfaccia CVS +GenericName[ja]=CVS フロントエンド +GenericName[kk]=CVS интерфейсі +GenericName[lt]=CVS naudotojo sąsaja +GenericName[lv]=CVS Frontends +GenericName[ms]=Bahagian Depan CVS +GenericName[nb]=CVS-grensesnitt +GenericName[nds]=CVS-Böversiet +GenericName[ne]=CVS फ्रन्टइन्ड +GenericName[nl]=CVS-hulpprogramma +GenericName[nn]=CVS-grensesnitt +GenericName[pa]=CVS ਮੁੱਖ ਝਲਕ +GenericName[pl]=Interfejs do CVS +GenericName[pt]=Interface de CVS +GenericName[pt_BR]=Interface para o CVS +GenericName[ro]=Interfaţă grafică pentru CVS +GenericName[ru]=Работа с репозиториями CVS +GenericName[sk]=Rozhranie pre CVS +GenericName[sl]=Vmesnik CVS +GenericName[sr]=Графички интерфејс за CVS +GenericName[sr@Latn]=Grafički interfejs za CVS +GenericName[sv]=CVS-gränssnitt +GenericName[ta]=CVS முன்பகுதி +GenericName[tg]=Интерфейс ба CVS +GenericName[th]=ฟร้อนต์เอนด์ CVS +GenericName[tr]=CVS Önyüzü +GenericName[uk]=Інтерфейс до CVS +GenericName[ven]=CVS yo iswa phanda +GenericName[xh]=CVS Isiqalo sesiphelo +GenericName[zh_CN]=CVS 前端 +GenericName[zh_TW]=CVS 前端 +GenericName[zu]=CVSIsiqalo sokugcina +Exec=cervisia -caption "%c" %i %m %u +Path= +Icon=cervisia +DocPath=cervisia/index.html +Type=Application +Terminal=false +MimeType=inode/directory; +Name=Cervisia +Name[hi]=सर्विसिया +Name[pa]=ਸਰਵੀਸੀਆ +Name[ta]= செர்வியா +Name[th]=เซอร์วิเซีย +ServiceTypes=KParts/ReadOnlyPart,Browser/View +X-KDE-Library=libcervisiapart +X-KDE-BrowserView-Args=DetailedList + +X-DCOP-ServiceType=Multi +Categories=Qt;KDE;Development; diff --git a/cervisia/cervisia.pod b/cervisia/cervisia.pod new file mode 100644 index 00000000..940ba8e0 --- /dev/null +++ b/cervisia/cervisia.pod @@ -0,0 +1,109 @@ +=head1 NAME + +Cervisia - Graphical CVS frontend + +=head1 SYNOPSIS + +B<cervisia> + S<[ B<--display> I<display> ]> + S<[ B<--caption> I<caption> ]> + S<[ B<--icon> I<icon> ]> + S<[ B<--miniicon> I<miniicon> ]> + S<[ B<--config> I<filename> ]> + S<[ B<--dcopserver> I<server> ]> + S<[ B<--nocrashhandler> ]> + S<[ B<--waitforwm> ]> + S<[ B<--style> I<style> ]> + S<[ B<--geometry> I<geometry> ]> + S<[ B<--resolve> I<filename> ]> + S<[ B<--log> I<filename> ]> + S<[ B<--annotate> I<filename> ]> + S<[ I<directory> ]> + +=head1 DESCRIPTION + +Cervisia is a graphical user interface for the Concurrent Versions +System. It is based on the KDE libraries and therefore shares +their Look'n'Feel, configuration and help system. + +=head1 OPTIONS + +Cervisia accepts the following options: + +=over + +=item I<directory> + +Tells Cervisia to open the sandbox in I<directory> at startup + +=item B<--resolve> I<filename> + +Shows a resolve dialog for the given file + +=item B<--log> I<filename> + +Shows a log dialog for the given file + +=item B<--annotate> I<filename> + +Shows a annotation dialog for the given file + +=item B<--caption> I<caption> + +Sets the caption, i. e. what is shown in the title bar + +=item B<--icon> I<icon> + +Sets the program's icon (used by window managers and panels) + +=item B<--miniicon> I<miniicon> + +Sets the program's mini icon (used by window managers and panels) + +=item B<--config> I<filename> + +Uses the given file for the configuration + +=item B<--dcopserver> I<server> + +Sets the dcopserver the program should use + +=item B<--nocrashhandler> + +Disables the crash handler. Use this to get core dumps + +=item B<--waitforwm> + +Waits for a WM_NET compatible windowmanager + +=item B<--style> I<style> + +Sets the application GUI style + +=item B<--geometry> I<geometry> + +Sets the geometry of the main window + +=head1 FILES + +F<_KDECONFDIR_/cervisiarc> - global configuration file + +F<$HOME/.kde/share/config/cervisiarc> - user-specific configuration file + + +=head1 SEE ALSO + +F<_KDEHTMLDIR_/en/cervisia/index.html> + +L<cvs(1)> + +F<http://cervisia.kde.org/> + +=head1 AUTHOR + +Cervisia was originally developed by Bernd Gehrmann and is now maintained by +Christian Loose <christian.loose@kdemail.net>. + +=head1 REPORTING BUGS + +Report bugs at http://bugs.kde.org. diff --git a/cervisia/cervisia.upd b/cervisia/cervisia.upd new file mode 100644 index 00000000..9c635ea1 --- /dev/null +++ b/cervisia/cervisia.upd @@ -0,0 +1,92 @@ +# +Id=kde3.2/20021228 +File=cervisiapartrc,cvsservicerc +Group=General +Options=copy +Key=CVSPath +Options=copy +Key=Compression +# +Id=kde3.2/20021229 +File=cervisiapartrc,cvsservicerc +Script=move_repositories.pl,perl +# +Id=kde3.2/20030116 +File=cervisiapartrc +RemoveGroup=Main window +File=cervisiarc +RemoveGroup=Main window +# +Id=kde3.2/20030117 +File=cervisiapartrc,cervisiarc +Group=Session +Key=Current Directory +# +Id=kde3.2/20030118 +File=cervisiapartrc +RemoveGroup=Annotate dialog +File=cervisiapartrc +Group=LogList view +Key=Columns,ColumnOrder +Key=ColumnSizes,ColumnWidths +RemoveKey=Customized +# +Id=kde3.2/20030205 +File=cervisiapartrc +Group=Log dialog,LogDialog +Key=ShowListTab +RemoveGroup=Log dialog +# +Id=kde3.2/20030210 +File=cervisiapartrc +RemoveGroup=ChangeLog dialog +# +Id=kde3.2/20030211 +File=cervisiapartrc +Group=Diff dialog,DiffDialog +Key=Sync +RemoveGroup=Diff dialog +# +Id=kde3.2/20030212 +File=cervisiapartrc +RemoveGroup=Commit dialog +RemoveGroup=History dialog +RemoveGroup=Resolve dialog +RemoveGroup=Resolve edit dialog +# +Id=kde3.2/20030216 +File=cervisiapartrc +RemoveGroup=AddRepository dialog +RemoveGroup=Repository dialog +Group=Checkout dialog,CheckoutDialog +AllKeys +# +Id=kde3.2/20030725 +File=cervisiapartrc +Group=LogDialog +RemoveKey=ShowListTab +# +Id=kde3.2/20030730 +File=cervisiapartrc +Group=Colors +Script=change_colors.pl,perl +# +Id=kde3.2/20030829 +File=cervisiapartrc +Group=Communication +RemoveKey=Editor +# +Id=kde3.2/20031014 +File=cervisiapartrc +Group=General +RemoveKey=CVSPath +RemoveKey=Compression +# +Id=kde3.4/20041112 +File=cvsservicerc +Script=cervisia-normalize_cvsroot.pl,perl +# +Id=kde3.4/20041207 +File=cervisiapartrc +Group=Repositories +Script=cervisia-change_repos_list.pl,perl diff --git a/cervisia/cervisiapart.cpp b/cervisia/cervisiapart.cpp new file mode 100644 index 00000000..56b37ece --- /dev/null +++ b/cervisia/cervisiapart.cpp @@ -0,0 +1,1939 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2002-2006 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <qlabel.h> +#include <qmessagebox.h> +#include <qpushbutton.h> +#include <qpopupmenu.h> +#include <qtextstream.h> +#include <qtooltip.h> +#include <kaboutdata.h> +#include <kaction.h> +#include <kapplication.h> +#include <kfiledialog.h> +#include <kinputdialog.h> +#include <kinstance.h> +#include <klocale.h> +#include <knotifyclient.h> +#include <kprocess.h> +#include <kpropertiesdialog.h> +#include <kstatusbar.h> +#include <kstdaction.h> +#include <kxmlguifactory.h> +#include <krun.h> +#include <kdebug.h> +#include <kmessagebox.h> +#include <kglobal.h> +#include <kio/netaccess.h> + +#include "progressdlg.h" +#include "logdlg.h" +#include "diffdlg.h" +#include "resolvedlg.h" +#include "annotatedlg.h" +#include "annotatectl.h" +#include "commitdlg.h" +#include "updatedlg.h" +#include "checkoutdlg.h" +#include "tagdlg.h" +#include "mergedlg.h" +#include "historydlg.h" +#include "updateview.h" +#include "updateview_items.h" +#include "protocolview.h" +#include "repositorydlg.h" +#include "settingsdlg.h" +#include "changelogdlg.h" +#include "watchersdlg.h" +#include "cvsinitdlg.h" +#include "misc.h" +#include "cvsservice_stub.h" +#include "repository_stub.h" +#include "globalignorelist.h" +#include "patchoptiondlg.h" +#include "editwithmenu.h" + +#include "cervisiapart.h" +#include "version.h" +#include "cervisiapart.moc" + +using Cervisia::TagDialog; + +#define COMMIT_SPLIT_CHAR '\r' + +K_EXPORT_COMPONENT_FACTORY( libcervisiapart, CervisiaFactory ) + +CervisiaPart::CervisiaPart( QWidget *parentWidget, const char *widgetName, + QObject *parent, const char *name, const QStringList& /*args*/ ) + : KParts::ReadOnlyPart( parent, name ) + , hasRunningJob( false ) + , opt_hideFiles( false ) + , opt_hideUpToDate( false ) + , opt_hideRemoved( false ) + , opt_hideNotInCVS( false ) + , opt_hideEmptyDirectories( false ) + , opt_createDirs( false ) + , opt_pruneDirs( false ) + , opt_updateRecursive( true ) + , opt_commitRecursive( true ) + , opt_doCVSEdit( false ) + , recent( 0 ) + , cvsService( 0 ) + , m_statusBar(new KParts::StatusBarExtension(this)) + , m_browserExt( 0 ) + , filterLabel( 0 ) + , m_editWithId(0) + , m_currentEditMenu(0) + , m_jobType(Unknown) +{ + KGlobal::locale()->insertCatalogue("cervisia"); + + setInstance( CervisiaFactory::instance() ); + m_browserExt = new CervisiaBrowserExtension( this ); + + // start the cvs DCOP service + QString error; + QCString appId; + if( KApplication::startServiceByDesktopName("cvsservice", QStringList(), &error, &appId) ) + { + KMessageBox::sorry(0, i18n("Starting cvsservice failed with message: ") + + error, "Cervisia"); + } + else + // create a reference to the service + cvsService = new CvsService_stub(appId, "CvsService"); + + // Create UI + KConfig *conf = config(); + conf->setGroup("LookAndFeel"); + bool splitHorz = conf->readBoolEntry("SplitHorizontally",true); + + // When we couldn't start the DCOP service, we just display a QLabel with + // an explaination + if( cvsService ) + { + Orientation o = splitHorz ? QSplitter::Vertical + : QSplitter::Horizontal; + splitter = new QSplitter(o, parentWidget, widgetName); + // avoid PartManager's warning that Part's window can't handle focus + splitter->setFocusPolicy( QWidget::StrongFocus ); + + update = new UpdateView(*config(), splitter); + update->setFocusPolicy( QWidget::StrongFocus ); + update->setFocus(); + connect( update, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)), + this, SLOT(popupRequested(KListView*, QListViewItem*, const QPoint&)) ); + connect( update, SIGNAL(fileOpened(QString)), + this, SLOT(openFile(QString)) ); + + protocol = new ProtocolView(appId, splitter); + protocol->setFocusPolicy( QWidget::StrongFocus ); + + setWidget(splitter); + } + else + setWidget(new QLabel(i18n("This KPart is non-functional, because the " + "cvs DCOP service could not be started."), + parentWidget)); + + if( cvsService ) + { + setupActions(); + readSettings(); + connect( update, SIGNAL( selectionChanged() ), this, SLOT( updateActions() ) ); + } + + setXMLFile( "cervisiaui.rc" ); + + QTimer::singleShot(0, this, SLOT(slotSetupStatusBar())); +} + +CervisiaPart::~CervisiaPart() +{ + // stop the cvs DCOP service and delete reference + if( cvsService ) + cvsService->quit(); + delete cvsService; + + if( cvsService ) + writeSettings(); +} + +KConfig *CervisiaPart::config() +{ + return CervisiaFactory::instance()->config(); +} + +bool CervisiaPart::openURL( const KURL &u ) +{ + // support url protocols like system:// or home:// + KURL url = KIO::NetAccess::mostLocalURL(u, widget()); + + // right now, we are unfortunately not network-aware + if( !url.isLocalFile() ) + { + KMessageBox::sorry(widget(), + i18n("Remote CVS working folders are not " + "supported."), + "Cervisia"); + return false; + } + + if( hasRunningJob ) + { + KMessageBox::sorry(widget(), + i18n("You cannot change to a different folder " + "while there is a running cvs job."), + "Cervisia"); + return false; + } + + return openSandbox( url.path() ); +} + + +void CervisiaPart::slotSetupStatusBar() +{ + // create the active filter indicator and add it to the statusbar + filterLabel = new QLabel("UR", m_statusBar->statusBar()); + filterLabel->setFixedSize(filterLabel->sizeHint()); + filterLabel->setText(""); + QToolTip::add(filterLabel, + i18n("F - All files are hidden, the tree shows only folders\n" + "N - All up-to-date files are hidden\n" + "R - All removed files are hidden")); + m_statusBar->addStatusBarItem(filterLabel, 0, true); +} + +void CervisiaPart::setupActions() +{ + KAction *action; + QString hint; + + actionCollection()->setHighlightingEnabled(true); + + // + // File Menu + // + action = new KAction( i18n("O&pen Sandbox..."), "fileopen", CTRL+Key_O, + this, SLOT( slotOpenSandbox() ), + actionCollection(), "file_open" ); + hint = i18n("Opens a CVS working folder in the main window"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + recent = new KRecentFilesAction( i18n("Recent Sandboxes"), 0, + this, SLOT( openURL( const KURL & ) ), + actionCollection(), "file_open_recent" ); + + action = new KAction( i18n("&Insert ChangeLog Entry..."), 0, + this, SLOT( slotChangeLog() ), + actionCollection(), "insert_changelog_entry" ); + hint = i18n("Inserts a new intro into the file ChangeLog in the toplevel folder"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Update"), "vcs_update", CTRL+Key_U, + this, SLOT( slotUpdate() ), + actionCollection(), "file_update" ); + hint = i18n("Updates (cvs update) the selected files and folders"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Status"), "vcs_status", Key_F5, + this, SLOT( slotStatus() ), + actionCollection(), "file_status" ); + hint = i18n("Updates the status (cvs -n update) of the selected files and folders"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Edit"), 0, + this, SLOT( slotOpen() ), + actionCollection(), "file_edit" ); + hint = i18n("Opens the marked file for editing"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("Reso&lve..."), 0, + this, SLOT( slotResolve() ), + actionCollection(), "file_resolve" ); + hint = i18n("Opens the resolve dialog with the selected file"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Commit..."), "vcs_commit", Key_NumberSign, + this, SLOT( slotCommit() ), + actionCollection(), "file_commit" ); + hint = i18n("Commits the selected files"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Add to Repository..."), "vcs_add", Key_Insert, + this, SLOT( slotAdd() ), + actionCollection(), "file_add" ); + hint = i18n("Adds (cvs add) the selected files to the repository"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("Add &Binary..."), 0, + this, SLOT( slotAddBinary() ), + actionCollection(), "file_add_binary" ); + hint = i18n("Adds (cvs -kb add) the selected files as binaries to the repository"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Remove From Repository..."), "vcs_remove", Key_Delete, + this, SLOT( slotRemove() ), + actionCollection(), "file_remove" ); + hint = i18n("Removes (cvs remove) the selected files from the repository"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("Rever&t"), 0, + this, SLOT( slotRevert() ), + actionCollection(), "file_revert_local_changes" ); + hint = i18n("Reverts (cvs update -C) the selected files (only cvs 1.11)"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + // context menu only + action = new KAction( i18n("&Properties"), 0, + this, SLOT( slotFileProperties() ), + actionCollection(), "file_properties" ); + + // + // View Menu + // + action = new KAction( i18n("Stop"), "stop", Key_Escape, + protocol, SLOT(cancelJob()), + actionCollection(), "stop_job" ); + action->setEnabled( false ); + hint = i18n("Stops any running sub-processes"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + + action = new KAction( i18n("Browse &Log..."), CTRL+Key_L, + this, SLOT(slotBrowseLog()), + actionCollection(), "view_log" ); + hint = i18n("Shows the revision tree of the selected file"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + +#if 0 + action = new KAction( i18n("Browse Multi-File Log..."), 0, + this, SLOT(slotBrowseMultiLog()), + actionCollection() ); +#endif + action = new KAction( i18n("&Annotate..."), CTRL+Key_A, + this, SLOT(slotAnnotate()), + actionCollection(), "view_annotate" ); + hint = i18n("Shows a blame-annotated view of the selected file"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Difference to Repository (BASE)..."), "vcs_diff", CTRL+Key_D, + this, SLOT(slotDiffBase()), + actionCollection(), "view_diff_base" ); + hint = i18n("Shows the differences of the selected file to the checked out version (tag BASE)"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("Difference to Repository (HEAD)..."), "vcs_diff", CTRL+Key_H, + this, SLOT(slotDiffHead()), + actionCollection(), "view_diff_head" ); + hint = i18n("Shows the differences of the selected file to the newest version in the repository (tag HEAD)"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("Last &Change..."), 0, + this, SLOT(slotLastChange()), + actionCollection(), "view_last_change" ); + hint = i18n("Shows the differences between the last two revisions of the selected file"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&History..."), 0, + this, SLOT(slotHistory()), + actionCollection(), "view_history" ); + hint = i18n("Shows the CVS history as reported by the server"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Unfold File Tree"), 0, + this , SLOT(slotUnfoldTree()), + actionCollection(), "view_unfold_tree" ); + + hint = i18n("Opens all branches of the file tree"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Fold File Tree"), 0, + this, SLOT(slotFoldTree()), + actionCollection(), "view_fold_tree" ); + hint = i18n("Closes all branches of the file tree"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + // + // Advanced Menu + // + action = new KAction( i18n("&Tag/Branch..."), 0, + this, SLOT(slotCreateTag()), + actionCollection(), "create_tag" ); + hint = i18n("Creates a tag or branch for the selected files"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Delete Tag..."), 0, + this, SLOT(slotDeleteTag()), + actionCollection(), "delete_tag" ); + hint = i18n("Deletes a tag from the selected files"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Update to Tag/Date..."), 0, + this, SLOT(slotUpdateToTag()), + actionCollection(), "update_to_tag" ); + hint = i18n("Updates the selected files to a given tag, branch or date"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("Update to &HEAD"), 0, + this, SLOT(slotUpdateToHead()), + actionCollection(), "update_to_head" ); + hint = i18n("Updates the selected files to the HEAD revision"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Merge..."), 0, + this, SLOT(slotMerge()), + actionCollection(), "merge" ); + hint = i18n("Merges a branch or a set of modifications into the selected files"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Add Watch..."), 0, + this, SLOT(slotAddWatch()), + actionCollection(), "add_watch" ); + hint = i18n("Adds a watch for the selected files"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Remove Watch..."), 0, + this, SLOT(slotRemoveWatch()), + actionCollection(), "remove_watch" ); + hint = i18n("Removes a watch from the selected files"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("Show &Watchers"), 0, + this, SLOT(slotShowWatchers()), + actionCollection(), "show_watchers" ); + hint = i18n("Shows the watchers of the selected files"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("Ed&it Files"), 0, + this, SLOT(slotEdit()), + actionCollection(), "edit_files" ); + hint = i18n("Edits (cvs edit) the selected files"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("U&nedit Files"), 0, + this, SLOT(slotUnedit()), + actionCollection(), "unedit_files" ); + hint = i18n("Unedits (cvs unedit) the selected files"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("Show &Editors"), 0, + this, SLOT(slotShowEditors()), + actionCollection(), "show_editors" ); + hint = i18n("Shows the editors of the selected files"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Lock Files"), 0, + this, SLOT(slotLock()), + actionCollection(), "lock_files" ); + hint = i18n("Locks the selected files, so that others cannot modify them"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("Unl&ock Files"), 0, + this, SLOT(slotUnlock()), + actionCollection(), "unlock_files" ); + hint = i18n("Unlocks the selected files"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("Create &Patch Against Repository..."), 0, + this, SLOT(slotMakePatch()), + actionCollection(), "make_patch" ); + hint = i18n("Creates a patch from the modifications in your sandbox"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + // + // Repository Menu + // + action = new KAction( i18n("&Create..."), 0, + this, SLOT(slotCreateRepository()), + actionCollection(), "repository_create" ); + + action = new KAction( i18n("&Checkout..."), 0, + this, SLOT(slotCheckout()), + actionCollection(), "repository_checkout" ); + hint = i18n("Allows you to checkout a module from a repository"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Import..."), 0, + this, SLOT(slotImport()), + actionCollection(), "repository_import" ); + hint = i18n("Allows you to import a module into a repository"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("&Repositories..."), 0, + this, SLOT(slotRepositories()), + actionCollection(), "show_repositories" ); + hint = i18n("Configures a list of repositories you regularly use"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + // + // Settings menu + // + KToggleAction* toggaction = new KToggleAction( i18n("Hide All &Files"), 0, + this, SLOT(slotHideFiles()), + actionCollection(), "settings_hide_files" ); + toggaction->setCheckedState(i18n("Show All &Files")); + hint = i18n("Determines whether only folders are shown"); + toggaction->setToolTip( hint ); + toggaction->setWhatsThis( hint ); + + toggaction = new KToggleAction( i18n("Hide Unmodified Files"), 0, + this, SLOT(slotHideUpToDate()), + actionCollection(), "settings_hide_uptodate" ); + toggaction->setCheckedState(i18n("Show Unmodified Files")); + hint = i18n("Determines whether files with status up-to-date or " + "unknown are hidden"); + toggaction->setToolTip( hint ); + toggaction->setWhatsThis( hint ); + + toggaction = new KToggleAction( i18n("Hide Removed Files"), 0, + this, SLOT(slotHideRemoved()), + actionCollection(), "settings_hide_removed" ); + toggaction->setCheckedState(i18n("Show Removed Files")); + hint = i18n("Determines whether removed files are hidden"); + toggaction->setToolTip( hint ); + toggaction->setWhatsThis( hint ); + + toggaction = new KToggleAction( i18n("Hide Non-CVS Files"), 0, + this, SLOT(slotHideNotInCVS()), + actionCollection(), "settings_hide_notincvs" ); + toggaction->setCheckedState(i18n("Show Non-CVS Files")); + hint = i18n("Determines whether files not in CVS are hidden"); + toggaction->setToolTip( hint ); + toggaction->setWhatsThis( hint ); + + toggaction = new KToggleAction( i18n("Hide Empty Folders"), 0, + this, SLOT(slotHideEmptyDirectories()), + actionCollection(), "settings_hide_empty_directories" ); + toggaction->setCheckedState(i18n("Show Empty Folders")); + hint = i18n("Determines whether folders without visible entries are hidden"); + toggaction->setToolTip( hint ); + toggaction->setWhatsThis( hint ); + + action = new KToggleAction( i18n("Create &Folders on Update"), 0, + this, SLOT(slotCreateDirs()), + actionCollection(), "settings_create_dirs" ); + hint = i18n("Determines whether updates create folders"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KToggleAction( i18n("&Prune Empty Folders on Update"), 0, + this, SLOT(slotPruneDirs()), + actionCollection(), "settings_prune_dirs" ); + hint = i18n("Determines whether updates remove empty folders"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KToggleAction( i18n("&Update Recursively"), 0, + this, SLOT(slotUpdateRecursive()), + actionCollection(), "settings_update_recursively" ); + hint = i18n("Determines whether updates are recursive"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KToggleAction( i18n("C&ommit && Remove Recursively"), 0, + this, SLOT(slotCommitRecursive()), + actionCollection(), "settings_commit_recursively" ); + hint = i18n("Determines whether commits and removes are recursive"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KToggleAction( i18n("Do cvs &edit Automatically When Necessary"), 0, + this, SLOT(slotDoCVSEdit()), + actionCollection(), "settings_do_cvs_edit" ); + hint = i18n("Determines whether automatic cvs editing is active"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = new KAction( i18n("Configure Cervisia..."), "configure", 0, + this, SLOT(slotConfigure()), + actionCollection(), "configure_cervisia" ); + hint = i18n("Allows you to configure the Cervisia KPart"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + // + // Help Menu + // + action = KStdAction::help( this, SLOT(slotHelp()), + actionCollection() ); + + action = new KAction( i18n("CVS &Manual"), 0, + this, SLOT(slotCVSInfo()), + actionCollection(), "help_cvs_manual" ); + hint = i18n("Opens the help browser with the CVS documentation"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + // + // Folder context menu + // + toggaction = new KToggleAction( i18n("Unfold Folder"), 0, + this, SLOT( slotUnfoldFolder() ), + actionCollection(), "unfold_folder" ); + toggaction->setCheckedState(i18n("Fold Folder")); + + //action = KStdAction::aboutApp( this, SLOT(aboutCervisia()), + // actionCollection(), "help_about_cervisia" ); +} + + +void CervisiaPart::popupRequested(KListView*, QListViewItem* item, const QPoint& p) +{ + QString xmlName = "context_popup"; + + if( isDirItem(item) && update->fileSelection().isEmpty() ) + { + xmlName = "folder_context_popup"; + KToggleAction* action = static_cast<KToggleAction*>(actionCollection()->action("unfold_folder")); + action->setChecked(item->isOpen()); + } + + if( QPopupMenu* popup = static_cast<QPopupMenu*>(hostContainer(xmlName)) ) + { + if( isFileItem(item) ) + { + // remove old 'Edit with...' menu + if( m_editWithId && popup->findItem(m_editWithId) != 0 ) + { + popup->removeItem(m_editWithId); + delete m_currentEditMenu; + + m_editWithId = 0; + m_currentEditMenu = 0; + } + + // get name of selected file + QString selectedFile; + update->getSingleSelection(&selectedFile); + + if( !selectedFile.isEmpty() ) + { + KURL u; + u.setPath(sandbox + "/" + selectedFile); + + m_currentEditMenu = new Cervisia::EditWithMenu(u, popup); + + if( m_currentEditMenu->menu() ) + m_editWithId = popup->insertItem(i18n("Edit With"), + m_currentEditMenu->menu(), -1, 1); + } + } + + popup->exec(p); + } + else + kdDebug(8050) << "CervisiaPart: can't get XML definition for " << xmlName << ", factory()=" << factory() << endl; +} + +void CervisiaPart::updateActions() +{ + bool hassandbox = !sandbox.isEmpty(); + stateChanged("has_sandbox", hassandbox ? StateNoReverse : StateReverse); + + bool single = update->hasSingleSelection(); + stateChanged("has_single_selection", single ? StateNoReverse + : StateReverse); + + bool singleFolder = (update->multipleSelection().count() == 1); + stateChanged("has_single_folder", singleFolder ? StateNoReverse + : StateReverse); + + m_browserExt->setPropertiesActionEnabled(single); + + // bool nojob = !( actionCollection()->action( "stop_job" )->isEnabled() ); + bool selected = (update->currentItem() != 0); + bool nojob = !hasRunningJob && selected; + + stateChanged("item_selected", selected ? StateNoReverse : StateReverse); + stateChanged("has_no_job", nojob ? StateNoReverse : StateReverse); + stateChanged("has_running_job", hasRunningJob ? StateNoReverse + : StateReverse); + +} + + +void CervisiaPart::aboutCervisia() +{ + QString aboutstr(i18n("Cervisia %1\n" + "(Using KDE %2)\n" + "\n" + "Copyright (c) 1999-2002\n" + "Bernd Gehrmann <bernd@mail.berlios.de>\n" + "\n" + "This program is free software; you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation; either version 2 of the License, or\n" + "(at your option) any later version.\n" + "This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "See the ChangeLog file for a list of contributors.")); + QMessageBox::about(0, i18n("About Cervisia"), + aboutstr.arg(CERVISIA_VERSION).arg(KDE_VERSION_STRING)); +} + + +KAboutData* CervisiaPart::createAboutData() +{ + KAboutData* about = new KAboutData( + "cervisiapart", I18N_NOOP("Cervisia Part"), + CERVISIA_VERSION, I18N_NOOP("A CVS frontend"), + KAboutData::License_GPL, + I18N_NOOP("Copyright (c) 1999-2002 Bernd Gehrmann"), 0, + "http://www.kde.org/apps/cervisia"); + + about->addAuthor("Bernd Gehrmann", I18N_NOOP("Original author and former " + "maintainer"), "bernd@mail.berlios.de", 0); + about->addAuthor("Christian Loose", I18N_NOOP("Maintainer"), + "christian.loose@hamburg.de", 0); + about->addAuthor("Andr\303\251 W\303\266bbeking", I18N_NOOP("Developer"), + "woebbeking@web.de", 0); + + about->addCredit("Richard Moore", I18N_NOOP("Conversion to KPart"), + "rich@kde.org", 0); + + return about; +} + + +void CervisiaPart::slotOpenSandbox() +{ + QString dirname = KFileDialog::getExistingDirectory(":CervisiaPart", widget(), + i18n("Open Sandbox")); + if (dirname.isEmpty()) + return; + + openSandbox(dirname); +} + + +void CervisiaPart::slotChangeLog() +{ + // Modal dialog + ChangeLogDialog dlg(*config(), widget()); + if (dlg.readFile(sandbox + "/ChangeLog")) + { + if (dlg.exec()) + changelogstr = dlg.message(); + } +} + + +void CervisiaPart::slotOpen() +{ + QStringList filenames = update->fileSelection(); + if (filenames.isEmpty()) + return; + openFiles(filenames); +} + + +void CervisiaPart::openFile(QString filename) +{ + QStringList files; + files << filename; + openFiles(files); +} + + +void CervisiaPart::openFiles(const QStringList &filenames) +{ + // call cvs edit automatically? + if( opt_doCVSEdit ) + { + QStringList files; + + // only edit read-only files + QStringList::ConstIterator it = filenames.begin(); + QStringList::ConstIterator end = filenames.end(); + for( ; it != end; ++it ) + { + if( !QFileInfo(*it).isWritable() ) + files << *it; + } + + if( files.count() ) + { + DCOPRef job = cvsService->edit(files); + + ProgressDialog dlg(widget(), "Edit", job, "edit", i18n("CVS Edit")); + if( !dlg.execute() ) + return; + } + } + + // Now open the files by using KRun + QDir dir(sandbox); + + QStringList::ConstIterator it = filenames.begin(); + QStringList::ConstIterator end = filenames.end(); + for( ; it != end; ++it ) + { + KURL u; + u.setPath(dir.absFilePath(*it)); + KRun* run = new KRun(u, 0, true, false); + run->setRunExecutables(false); + } +} + + +void CervisiaPart::slotResolve() +{ + QString filename; + update->getSingleSelection(&filename); + if (filename.isEmpty()) + return; + + // Non-modal dialog + ResolveDialog *l = new ResolveDialog(*config()); + if (l->parseFile(filename)) + l->show(); + else + delete l; +} + + +void CervisiaPart::slotUpdate() +{ + updateSandbox(); +} + + +void CervisiaPart::slotStatus() +{ + QStringList list = update->multipleSelection(); + if (list.isEmpty()) + return; + + // Bug #105097: Embedded in Konqueror, all Cervisia tabs share + // a common current directory. This confuses UpdateView. That's + // why we always change the current directory here. + QDir::setCurrent(sandbox); + + update->prepareJob(opt_updateRecursive, UpdateView::UpdateNoAct); + + DCOPRef cvsJob = cvsService->simulateUpdate(list, opt_updateRecursive, + opt_createDirs, opt_pruneDirs); + + // get command line from cvs job + QString cmdline; + DCOPReply reply = cvsJob.call("cvsCommand()"); + if( reply.isValid() ) + reply.get<QString>(cmdline); + + if( protocol->startJob(true) ) + { + showJobStart(cmdline); + connect( protocol, SIGNAL(receivedLine(QString)), update, SLOT(processUpdateLine(QString)) ); + connect( protocol, SIGNAL(jobFinished(bool, int)), update, SLOT(finishJob(bool, int)) ); + connect( protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()) ); + } +} + + +void CervisiaPart::slotUpdateToTag() +{ + UpdateDialog *l = new UpdateDialog(cvsService, widget() ); + + if (l->exec()) + { + QString tagopt; + if (l->byTag()) + { + tagopt = "-r "; + tagopt += l->tag(); + } + else + { + tagopt = "-D "; + tagopt += KProcess::quote(l->date()); + } + tagopt += " "; + updateSandbox(tagopt); + } + delete l; +} + + +void CervisiaPart::slotUpdateToHead() +{ + updateSandbox("-A"); +} + + +void CervisiaPart::slotRevert() +{ + updateSandbox("-C"); +} + + +void CervisiaPart::slotMerge() +{ + MergeDialog dlg(cvsService, widget()); + + if (dlg.exec()) + { + QString tagopt; + if (dlg.byBranch()) + { + tagopt = "-j "; + tagopt += dlg.branch(); + } + else + { + tagopt = "-j "; + tagopt += dlg.tag1(); + tagopt += " -j "; + tagopt += dlg.tag2(); + } + tagopt += " "; + updateSandbox(tagopt); + } +} + + +void CervisiaPart::slotCommit() +{ + QStringList list = update->multipleSelection(); + if (list.isEmpty()) + return; + + // modal dialog + CommitDialog dlg(*config(), cvsService, widget()); + dlg.setLogMessage(changelogstr); + dlg.setLogHistory(recentCommits); + dlg.setFileList(list); + + if (dlg.exec()) + { + // get new list of files + list = dlg.fileList(); + if( list.isEmpty() ) + return; + + QString msg = dlg.logMessage(); + if( !recentCommits.contains( msg ) ) + { + recentCommits.prepend( msg ); + while (recentCommits.count() > 50) + recentCommits.remove( recentCommits.last() ); + + KConfig* conf = config(); + conf->setGroup( "CommitLogs" ); + conf->writeEntry( sandbox, recentCommits, COMMIT_SPLIT_CHAR ); + } + + update->prepareJob(opt_commitRecursive, UpdateView::Commit); + + DCOPRef cvsJob = cvsService->commit(list, dlg.logMessage(), + opt_commitRecursive); + + // get command line from cvs job + QString cmdline = cvsJob.call("cvsCommand()"); + + if( protocol->startJob() ) + { + m_jobType = Commit; + showJobStart(cmdline); + connect( protocol, SIGNAL(jobFinished(bool, int)), update, SLOT(finishJob(bool, int)) ); + connect( protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()) ); + } + } +} + + +void CervisiaPart::slotAdd() +{ + addOrRemove(AddRemoveDialog::Add); +} + + +void CervisiaPart::slotAddBinary() +{ + addOrRemove(AddRemoveDialog::AddBinary); +} + + +void CervisiaPart::slotRemove() +{ + addOrRemove(AddRemoveDialog::Remove); +} + + +void CervisiaPart::slotFileProperties() +{ + QString filename; + update->getSingleSelection(&filename); + if( filename.isEmpty() ) + return; + + // Create URL from selected filename + QDir dir(sandbox); + + KURL u; + u.setPath(dir.absFilePath(filename)); + + // show file properties dialog + (void)new KPropertiesDialog(u); +} + + +void CervisiaPart::updateSandbox(const QString &extraopt) +{ + QStringList list = update->multipleSelection(); + if (list.isEmpty()) + return; + + // Bug #105097: Embedded in Konqueror, all Cervisia tabs share + // a common current directory. This confuses UpdateView. That's + // why we always change the current directory here. + QDir::setCurrent(sandbox); + + update->prepareJob(opt_updateRecursive, UpdateView::Update); + + DCOPRef cvsJob = cvsService->update(list, opt_updateRecursive, + opt_createDirs, opt_pruneDirs, extraopt); + + // get command line from cvs job + QString cmdline; + DCOPReply reply = cvsJob.call("cvsCommand()"); + if( reply.isValid() ) + reply.get<QString>(cmdline); + + if( protocol->startJob(true) ) + { + showJobStart(cmdline); + connect( protocol, SIGNAL(receivedLine(QString)), update, SLOT(processUpdateLine(QString)) ); + connect( protocol, SIGNAL(jobFinished(bool, int)), update, SLOT(finishJob(bool, int)) ); + connect( protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()) ); + } +} + + +void CervisiaPart::addOrRemove(AddRemoveDialog::ActionType action) +{ + QStringList list = update->multipleSelection(); + if (list.isEmpty()) + return; + + // modal dialog + AddRemoveDialog dlg(action, widget()); + dlg.setFileList(list); + + if (dlg.exec()) + { + DCOPRef cvsJob; + + switch (action) + { + case AddRemoveDialog::Add: + update->prepareJob(false, UpdateView::Add); + cvsJob = cvsService->add(list, false); + break; + + case AddRemoveDialog::AddBinary: + update->prepareJob(false, UpdateView::Add); + cvsJob = cvsService->add(list, true); + break; + + case AddRemoveDialog::Remove: + update->prepareJob(opt_commitRecursive, UpdateView::Remove); + cvsJob = cvsService->remove(list, opt_commitRecursive); + break; + } + + // get command line from cvs job + QString cmdline; + DCOPReply reply = cvsJob.call("cvsCommand()"); + if( reply.isValid() ) + reply.get<QString>(cmdline); + + if (protocol->startJob()) + { + showJobStart(cmdline); + connect( protocol, SIGNAL(jobFinished(bool, int)), + update, SLOT(finishJob(bool, int)) ); + connect( protocol, SIGNAL(jobFinished(bool, int)), + this, SLOT(slotJobFinished()) ); + } + } +} + +void CervisiaPart::slotBrowseLog() +{ + QString filename; + update->getSingleSelection(&filename); + if (filename.isEmpty()) + return; + + // Non-modal dialog + LogDialog *l = new LogDialog(*CervisiaPart::config()); + if (l->parseCvsLog(cvsService, filename)) + l->show(); + else + delete l; +} + + +#if 0 +void CervisiaPart::slotBrowseMultiLog() +{ + QStrList list = update->multipleSelection(); + if (!list.isEmpty()) + { + // Non-modal dialog + MultiLogDialog *l = new MultiLogDialog(); + if (l->parseCvsLog(".", list)) + l->show(); + else + delete l; + } +} +#endif + + +void CervisiaPart::slotAnnotate() +{ + QString filename; + update->getSingleSelection(&filename); + + if (filename.isEmpty()) + return; + + // Non-modal dialog + AnnotateDialog* dlg = new AnnotateDialog(*config()); + AnnotateController ctl(dlg, cvsService); + ctl.showDialog(filename); +} + + +void CervisiaPart::slotDiffBase() +{ + showDiff(QString::fromLatin1("BASE")); +} + + +void CervisiaPart::slotDiffHead() +{ + showDiff(QString::fromLatin1("HEAD")); +} + + +void CervisiaPart::slotAddWatch() +{ + addOrRemoveWatch(WatchDialog::Add); +} + + +void CervisiaPart::slotRemoveWatch() +{ + addOrRemoveWatch(WatchDialog::Remove); +} + + +void CervisiaPart::addOrRemoveWatch(WatchDialog::ActionType action) +{ + QStringList list = update->multipleSelection(); + if (list.isEmpty()) + return; + + WatchDialog dlg(action, widget()); + + if (dlg.exec() && dlg.events() != WatchDialog::None) + { + DCOPRef cvsJob; + + if (action == WatchDialog::Add) + cvsJob = cvsService->addWatch(list, dlg.events()); + else + cvsJob = cvsService->removeWatch(list, dlg.events()); + + // get command line from cvs job + QString cmdline = cvsJob.call("cvsCommand()"); + + if( protocol->startJob() ) + { + showJobStart(cmdline); + connect( protocol, SIGNAL(jobFinished(bool, int)), + this, SLOT(slotJobFinished()) ); + } + } +} + + +void CervisiaPart::slotShowWatchers() +{ + QStringList list = update->multipleSelection(); + if (list.isEmpty()) + return; + + // Non-modal dialog + WatchersDialog* dlg = new WatchersDialog(*config()); + if( dlg->parseWatchers(cvsService, list) ) + dlg->show(); + else + delete dlg; +} + + +void CervisiaPart::slotEdit() +{ + QStringList list = update->multipleSelection(); + if (list.isEmpty()) + return; + + DCOPRef cvsJob = cvsService->edit(list); + + // get command line from cvs job + QString cmdline = cvsJob.call("cvsCommand()"); + + if( protocol->startJob() ) + { + showJobStart(cmdline); + connect( protocol, SIGNAL(jobFinished(bool, int)), + this, SLOT(slotJobFinished()) ); + } +} + + +void CervisiaPart::slotUnedit() +{ + QStringList list = update->multipleSelection(); + if (list.isEmpty()) + return; + + DCOPRef cvsJob = cvsService->unedit(list); + + // get command line from cvs job + QString cmdline = cvsJob.call("cvsCommand()"); + + if( protocol->startJob() ) + { + showJobStart(cmdline); + connect( protocol, SIGNAL(jobFinished(bool, int)), + this, SLOT(slotJobFinished()) ); + } +} + + +void CervisiaPart::slotLock() +{ + QStringList list = update->multipleSelection(); + if (list.isEmpty()) + return; + + DCOPRef cvsJob = cvsService->lock(list); + + // get command line from cvs job + QString cmdline = cvsJob.call("cvsCommand()"); + + if( protocol->startJob() ) + { + showJobStart(cmdline); + connect( protocol, SIGNAL(jobFinished(bool, int)), + this, SLOT(slotJobFinished()) ); + } +} + + +void CervisiaPart::slotUnlock() +{ + QStringList list = update->multipleSelection(); + if (list.isEmpty()) + return; + + DCOPRef cvsJob = cvsService->unlock(list); + + // get command line from cvs job + QString cmdline = cvsJob.call("cvsCommand()"); + + if( protocol->startJob() ) + { + showJobStart(cmdline); + connect( protocol, SIGNAL(jobFinished(bool, int)), + this, SLOT(slotJobFinished()) ); + } +} + + +void CervisiaPart::slotShowEditors() +{ + QStringList list = update->multipleSelection(); + if (list.isEmpty()) + return; + + DCOPRef cvsJob = cvsService->editors(list); + + // get command line from cvs job + QString cmdline = cvsJob.call("cvsCommand()"); + + if( protocol->startJob() ) + { + showJobStart(cmdline); + connect( protocol, SIGNAL(jobFinished(bool, int)), + this, SLOT(slotJobFinished()) ); + } +} + + +void CervisiaPart::slotMakePatch() +{ + Cervisia::PatchOptionDialog optionDlg; + if( optionDlg.exec() == KDialogBase::Rejected ) + return; + + QString format = optionDlg.formatOption(); + QString diffOptions = optionDlg.diffOptions(); + + DCOPRef job = cvsService->makePatch(diffOptions, format); + if( !cvsService->ok() ) + return; + + ProgressDialog dlg(widget(), "Diff", job, "", i18n("CVS Diff")); + if( !dlg.execute() ) + return; + + QString fileName = KFileDialog::getSaveFileName(); + if( fileName.isEmpty() ) + return; + + if( !Cervisia::CheckOverwrite(fileName) ) + return; + + QFile f(fileName); + if( !f.open(IO_WriteOnly) ) + { + KMessageBox::sorry(widget(), + i18n("Could not open file for writing."), + "Cervisia"); + return; + } + + QTextStream t(&f); + QString line; + while( dlg.getLine(line) ) + t << line << '\n'; + + f.close(); +} + + +void CervisiaPart::slotImport() +{ + CheckoutDialog dlg(*config(), cvsService, CheckoutDialog::Import, widget()); + + if( !dlg.exec() ) + return; + + DCOPRef cvsJob = cvsService->import(dlg.workingDirectory(), dlg.repository(), + dlg.module(), dlg.ignoreFiles(), + dlg.comment(), dlg.vendorTag(), + dlg.releaseTag(), dlg.importBinary(), + dlg.useModificationTime()); + + // get command line from cvs job + QString cmdline = cvsJob.call("cvsCommand()"); + + if( protocol->startJob() ) + { + showJobStart(cmdline); + connect( protocol, SIGNAL(jobFinished(bool, int)), + this, SLOT(slotJobFinished()) ); + } +} + + +void CervisiaPart::slotCreateRepository() +{ + Cervisia::CvsInitDialog dlg(widget()); + + if( !dlg.exec() ) + return; + + DCOPRef cvsJob = cvsService->createRepository(dlg.directory()); + + QString cmdline = cvsJob.call("cvsCommand()"); + + if( protocol->startJob() ) + { + showJobStart(cmdline); + connect( protocol, SIGNAL(jobFinished(bool, int)), + this, SLOT(slotJobFinished()) ); + } +} + + +void CervisiaPart::slotCheckout() +{ + CheckoutDialog dlg(*config(), cvsService, CheckoutDialog::Checkout, widget()); + + if( !dlg.exec() ) + return; + + DCOPRef cvsJob = cvsService->checkout(dlg.workingDirectory(), dlg.repository(), + dlg.module(), dlg.branch(), opt_pruneDirs, + dlg.alias(), dlg.exportOnly(), dlg.recursive()); + + // get command line from cvs job + QString cmdline = cvsJob.call("cvsCommand()"); + + if( protocol->startJob() ) + { + showJobStart(cmdline); + connect( protocol, SIGNAL(jobFinished(bool, int)), + this, SLOT(slotJobFinished()) ); + } +} + + +void CervisiaPart::slotRepositories() +{ + RepositoryDialog *l = new RepositoryDialog(*config(), cvsService, widget()); + l->show(); +} + + +void CervisiaPart::slotCreateTag() +{ + createOrDeleteTag(TagDialog::Create); +} + + +void CervisiaPart::slotDeleteTag() +{ + createOrDeleteTag(TagDialog::Delete); +} + + +void CervisiaPart::createOrDeleteTag(TagDialog::ActionType action) +{ + QStringList list = update->multipleSelection(); + if (list.isEmpty()) + return; + + TagDialog dlg(action, cvsService, widget()); + + if (dlg.exec()) + { + DCOPRef cvsJob; + + if( action == TagDialog::Create ) + cvsJob = cvsService->createTag(list, dlg.tag(), dlg.branchTag(), + dlg.forceTag()); + else + cvsJob = cvsService->deleteTag(list, dlg.tag(), dlg.branchTag(), + dlg.forceTag()); + + // get command line from cvs job + QString cmdline = cvsJob.call("cvsCommand()"); + + if( protocol->startJob() ) + { + showJobStart(cmdline); + connect( protocol, SIGNAL(jobFinished(bool, int)), + this, SLOT(slotJobFinished()) ); + } + } +} + + + +void CervisiaPart::slotLastChange() +{ + QString filename, revA, revB; + update->getSingleSelection(&filename, &revA); + if (filename.isEmpty()) + return; + + int pos, lastnumber; + bool ok; + if ( (pos = revA.findRev('.')) == -1 + || (lastnumber=revA.right(revA.length()-pos-1).toUInt(&ok), !ok) ) + { + KMessageBox::sorry(widget(), + i18n("The revision looks invalid."), + "Cervisia"); + return; + } + if (lastnumber == 0) + { + KMessageBox::sorry(widget(), + i18n("This is the first revision of the branch."), + "Cervisia"); + return; + } + revB = revA.left(pos+1); + revB += QString::number(lastnumber-1); + + // Non-modal dialog + DiffDialog *l = new DiffDialog(*config()); + if (l->parseCvsDiff(cvsService, filename, revB, revA)) + l->show(); + else + delete l; +} + + +void CervisiaPart::slotHistory() +{ + // Non-modal dialog + HistoryDialog *l = new HistoryDialog(*config()); + if (l->parseHistory(cvsService)) + l->show(); + else + delete l; +} + + +void CervisiaPart::slotHideFiles() +{ + opt_hideFiles = !opt_hideFiles; + setFilter(); +} + + +void CervisiaPart::slotHideUpToDate() +{ + opt_hideUpToDate = !opt_hideUpToDate; + setFilter(); +} + + +void CervisiaPart::slotHideRemoved() +{ + opt_hideRemoved = !opt_hideRemoved; + setFilter(); +} + + +void CervisiaPart::slotHideNotInCVS() +{ + opt_hideNotInCVS = !opt_hideNotInCVS; + setFilter(); +} + + +void CervisiaPart::slotHideEmptyDirectories() +{ + opt_hideEmptyDirectories = !opt_hideEmptyDirectories; + setFilter(); +} + + +void CervisiaPart::slotFoldTree() +{ + update->foldTree(); + setFilter(); +} + +void CervisiaPart::slotUnfoldTree() +{ + update->unfoldTree(); + setFilter(); +} + + +void CervisiaPart::slotUnfoldFolder() +{ + update->unfoldSelectedFolders(); + setFilter(); +} + + +void CervisiaPart::slotCreateDirs() +{ + opt_createDirs = !opt_createDirs; +} + + +void CervisiaPart::slotPruneDirs() +{ + opt_pruneDirs = !opt_pruneDirs; +} + + +void CervisiaPart::slotUpdateRecursive() +{ + opt_updateRecursive = !opt_updateRecursive; +} + + +void CervisiaPart::slotCommitRecursive() +{ + opt_commitRecursive = !opt_commitRecursive; +} + + +void CervisiaPart::slotDoCVSEdit() +{ + opt_doCVSEdit = !opt_doCVSEdit; +} + +void CervisiaPart::slotConfigure() +{ + KConfig *conf = config(); + SettingsDialog *l = new SettingsDialog( conf, widget() ); + l->exec(); + + conf->setGroup("LookAndFeel"); + bool splitHorz = conf->readBoolEntry("SplitHorizontally",true); + splitter->setOrientation( splitHorz ? + QSplitter::Vertical : + QSplitter::Horizontal); +} + +void CervisiaPart::slotHelp() +{ + emit setStatusBarText( i18n("Invoking help on Cervisia") ); + KApplication::startServiceByDesktopName("khelpcenter", QString("help:/cervisia/index.html")); +} + + +void CervisiaPart::slotCVSInfo() +{ + emit setStatusBarText( i18n("Invoking help on CVS") ); + KApplication::startServiceByDesktopName("khelpcenter", QString("info:/cvs/Top")); +} + + +void CervisiaPart::showJobStart(const QString &cmdline) +{ + hasRunningJob = true; + actionCollection()->action( "stop_job" )->setEnabled( true ); + + emit setStatusBarText( cmdline ); + updateActions(); +} + + +void CervisiaPart::showDiff(const QString& revision) +{ + QString fileName; + update->getSingleSelection(&fileName); + + if (fileName.isEmpty()) + return; + + // Non-modal dialog + DiffDialog *l = new DiffDialog(*config()); + if (l->parseCvsDiff(cvsService, fileName, revision, QString::null)) + l->show(); + else + delete l; +} + + +void CervisiaPart::slotJobFinished() +{ + actionCollection()->action( "stop_job" )->setEnabled( false ); + hasRunningJob = false; + emit setStatusBarText( i18n("Done") ); + updateActions(); + + disconnect( protocol, SIGNAL(receivedLine(QString)), + update, SLOT(processUpdateLine(QString)) ); + + if( m_jobType == Commit ) + { + KNotifyClient::event(widget()->parentWidget()->winId(), "cvs_commit_done", + i18n("A CVS commit to repository %1 is done") + .arg(repository)); + m_jobType = Unknown; + } +} + + +bool CervisiaPart::openSandbox(const QString &dirname) +{ + // Do we have a cvs service? + if( !cvsService ) + return false; + + Repository_stub cvsRepository(cvsService->app(), "CvsRepository"); + + // change the working copy directory for the cvs DCOP service + bool opened = cvsRepository.setWorkingCopy(dirname); + + if( !cvsRepository.ok() || !opened ) + { + KMessageBox::sorry(widget(), + i18n("This is not a CVS folder.\n" + "If you did not intend to use Cervisia, you can " + "switch view modes within Konqueror."), + "Cervisia"); + + // remove path from recent sandbox menu + QFileInfo fi(dirname); + recent->removeURL( KURL::fromPathOrURL(fi.absFilePath()) ); + + return false; + } + + changelogstr = ""; + sandbox = ""; + repository = ""; + + // get path of sandbox for recent sandbox menu + sandbox = cvsRepository.workingCopy(); + recent->addURL( KURL::fromPathOrURL(sandbox) ); + + // get repository for the caption of the window + repository = cvsRepository.location(); + emit setWindowCaption(sandbox + "(" + repository + ")"); + + // set m_url member for tabbed window modus of Konqueror + m_url = KURL::fromPathOrURL(sandbox); + + // *NOTICE* + // The order is important here. We have to set the m_url member before + // calling this function because the progress dialog uses the enter_loop()/ + // exit_loop() methods. Those methods result in a call to queryExit() in + // cervisiashell.cpp which then uses the m_url member to save the last used + // directory. + if( cvsRepository.retrieveCvsignoreFile() ) + Cervisia::GlobalIgnoreList().retrieveServerIgnoreList(cvsService, + repository); + + QDir::setCurrent(sandbox); + update->openDirectory(sandbox); + setFilter(); + + KConfig *conf = config(); + conf->setGroup("General"); + bool dostatus = conf->readBoolEntry(repository.contains(":")? + "StatusForRemoteRepos" : + "StatusForLocalRepos", + false); + if (dostatus) + { + update->setSelected(update->firstChild(), true); + slotStatus(); + } + + //load the recentCommits for this app from the KConfig app + conf->setGroup( "CommitLogs" ); + recentCommits = conf->readListEntry( sandbox, COMMIT_SPLIT_CHAR ); + + return true; +} + + +void CervisiaPart::setFilter() +{ + UpdateView::Filter filter = UpdateView::Filter(0); + if (opt_hideFiles) + filter = UpdateView::Filter(filter | UpdateView::OnlyDirectories); + if (opt_hideUpToDate) + filter = UpdateView::Filter(filter | UpdateView::NoUpToDate); + if (opt_hideRemoved) + filter = UpdateView::Filter(filter | UpdateView::NoRemoved); + if (opt_hideNotInCVS) + filter = UpdateView::Filter(filter | UpdateView::NoNotInCVS); + if (opt_hideEmptyDirectories) + filter = UpdateView::Filter(filter | UpdateView::NoEmptyDirectories); + update->setFilter(filter); + + QString str; + if (opt_hideFiles) + str = "F"; + else + { + if (opt_hideUpToDate) + str += "N"; + if (opt_hideRemoved) + str += "R"; + } + + if( filterLabel ) + filterLabel->setText(str); +} + + +void CervisiaPart::readSettings() +{ + KConfig* config = CervisiaFactory::instance()->config(); + + config->setGroup("Session"); + recent->loadEntries( config ); + + // Unfortunately, the KConfig systems sucks and we have to live + // with all entries in one group for session management. + + opt_createDirs = config->readBoolEntry("Create Dirs", true); + (static_cast<KToggleAction *> (actionCollection()->action( "settings_create_dirs" ))) + ->setChecked( opt_createDirs ); + + opt_pruneDirs = config->readBoolEntry("Prune Dirs", true); + (static_cast<KToggleAction *> (actionCollection()->action( "settings_prune_dirs" ))) + ->setChecked( opt_pruneDirs ); + + opt_updateRecursive = config->readBoolEntry("Update Recursive", false); + (static_cast<KToggleAction *> (actionCollection()->action( "settings_update_recursively" ))) + ->setChecked( opt_updateRecursive ); + + opt_commitRecursive = config->readBoolEntry("Commit Recursive", false); + (static_cast<KToggleAction *> (actionCollection()->action( "settings_commit_recursively" ))) + ->setChecked( opt_commitRecursive ); + + opt_doCVSEdit = config->readBoolEntry("Do cvs edit", false); + (static_cast<KToggleAction *> (actionCollection()->action( "settings_do_cvs_edit" ))) + ->setChecked( opt_doCVSEdit ); + + opt_hideFiles = config->readBoolEntry("Hide Files", false); + (static_cast<KToggleAction *> (actionCollection()->action( "settings_hide_files" ))) + ->setChecked( opt_hideFiles ); + + opt_hideUpToDate = config->readBoolEntry("Hide UpToDate Files", false); + (static_cast<KToggleAction *> (actionCollection()->action( "settings_hide_uptodate" ))) + ->setChecked( opt_hideUpToDate ); + + opt_hideRemoved = config->readBoolEntry("Hide Removed Files", false); + (static_cast<KToggleAction *> (actionCollection()->action( "settings_hide_removed" ))) + ->setChecked( opt_hideRemoved ); + + opt_hideNotInCVS = config->readBoolEntry("Hide Non CVS Files", false); + (static_cast<KToggleAction *> (actionCollection()->action( "settings_hide_notincvs" ))) + ->setChecked( opt_hideNotInCVS ); + + opt_hideEmptyDirectories = config->readBoolEntry("Hide Empty Directories", false); + (static_cast<KToggleAction *> (actionCollection()->action( "settings_hide_empty_directories" ))) + ->setChecked( opt_hideEmptyDirectories ); + + setFilter(); + + int splitterpos1 = config->readNumEntry("Splitter Pos 1", 0); + int splitterpos2 = config->readNumEntry("Splitter Pos 2", 0); + if (splitterpos1) + { + QValueList<int> sizes; + sizes << splitterpos1; + sizes << splitterpos2; + splitter->setSizes(sizes); + } +} + + +void CervisiaPart::writeSettings() +{ + KConfig* config = CervisiaFactory::instance()->config(); + + config->setGroup("Session"); + recent->saveEntries( config ); + + config->writeEntry("Create Dirs", opt_createDirs); + config->writeEntry("Prune Dirs", opt_pruneDirs); + config->writeEntry("Update Recursive", opt_updateRecursive); + config->writeEntry("Commit Recursive", opt_commitRecursive); + config->writeEntry("Do cvs edit", opt_doCVSEdit); + config->writeEntry("Hide Files", opt_hideFiles); + config->writeEntry("Hide UpToDate Files", opt_hideUpToDate); + config->writeEntry("Hide Removed Files", opt_hideRemoved); + config->writeEntry("Hide Non CVS Files", opt_hideNotInCVS); + config->writeEntry("Hide Empty Directories", opt_hideEmptyDirectories); + QValueList<int> sizes = splitter->sizes(); + config->writeEntry("Splitter Pos 1", sizes[0]); + config->writeEntry("Splitter Pos 2", sizes[1]); + + // write to disk + config->sync(); +} + + +void CervisiaPart::guiActivateEvent(KParts::GUIActivateEvent* event) +{ + if( event->activated() && cvsService ) + { + // initial setup of the menu items' state + updateActions(); + } + + // don't call this as it overwrites Konqueror's caption (if you have a + // Konqueror with more than one view and switch back to Cervisia) + // + // KParts::ReadOnlyPart::guiActivateEvent(event); +} + + +CervisiaBrowserExtension::CervisiaBrowserExtension( CervisiaPart *p ) + : KParts::BrowserExtension( p, "CervisiaBrowserExtension" ) +{ + KGlobal::locale()->insertCatalogue("cervisia"); +} + +CervisiaBrowserExtension::~CervisiaBrowserExtension() +{ + +} + + +void CervisiaBrowserExtension::setPropertiesActionEnabled(bool enabled) +{ + emit enableAction("properties", enabled); +} + + +void CervisiaBrowserExtension::properties() +{ + static_cast<CervisiaPart*>(parent())->slotFileProperties(); +} + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/cervisiapart.h b/cervisia/cervisiapart.h new file mode 100644 index 00000000..e5881a8d --- /dev/null +++ b/cervisia/cervisiapart.h @@ -0,0 +1,219 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2002-2005 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef CERVISIAPART_H +#define CERVISIAPART_H + +#include <kparts/part.h> +#include <kparts/browserextension.h> +#include <kparts/genericfactory.h> +#include <kparts/statusbarextension.h> + +#include "addremovedlg.h" +#include "commitdlg.h" +#include "checkoutdlg.h" +#include "watchdlg.h" +#include "tagdlg.h" + +namespace Cervisia { class EditWithMenu; } +class QLabel; +class QListViewItem; +class QSplitter; +class QTimer; +class UpdateView; +class ProtocolView; +class KAboutData; +class KListView; +class KRecentFilesAction; +class CvsService_stub; +class CervisiaBrowserExtension; + + +/** + * An embeddable Cervisia viewer. + */ +class CervisiaPart : public KParts::ReadOnlyPart +{ + Q_OBJECT + +public: + CervisiaPart( QWidget *parentWidget, const char *widgetName, + QObject *parent, const char *name=0, const QStringList& args = QStringList()); + virtual ~CervisiaPart(); + + /** + * Get the config object for the part's instance. + */ + static KConfig *config(); + + QString sandBox() const { return sandbox; } + + static KAboutData* createAboutData(); + +public slots: + // unused because we overwrite the default behaviour of openURL() + virtual bool openFile() { return true; } + virtual bool openURL( const KURL & ); + + void openFile(QString filename); + void openFiles(const QStringList &filenames); + void popupRequested(KListView*, QListViewItem*, const QPoint&); + void updateActions(); + + void aboutCervisia(); + + void slotOpen(); + void slotResolve(); + void slotStatus(); + void slotUpdate(); + void slotChangeLog(); + void slotCommit(); + void slotAdd(); + void slotAddBinary(); + + void slotRemove(); + void slotFileProperties(); + void slotRevert(); + void slotBrowseLog(); + // void slotBrowseMultiLog(); + void slotAnnotate(); + void slotDiffBase(); + void slotDiffHead(); + void slotLastChange(); + void slotHistory(); + void slotCreateRepository(); + void slotCheckout(); + void slotImport(); + void slotRepositories(); + void slotCreateTag(); + void slotDeleteTag(); + void slotUpdateToTag(); + void slotUpdateToHead(); + void slotMerge(); + void slotAddWatch(); + void slotRemoveWatch(); + void slotShowWatchers(); + void slotEdit(); + void slotUnedit(); + void slotShowEditors(); + void slotLock(); + void slotUnlock(); + void slotMakePatch(); + void slotCreateDirs(); + void slotPruneDirs(); + void slotHideFiles(); + void slotHideUpToDate(); + void slotHideRemoved(); + + void slotHideNotInCVS(); + void slotHideEmptyDirectories(); + + void slotFoldTree(); + void slotUnfoldTree(); + void slotUnfoldFolder(); + + void slotUpdateRecursive(); + void slotCommitRecursive(); + void slotDoCVSEdit(); + void slotConfigure(); + void slotHelp(); + void slotCVSInfo(); + +protected slots: + void slotJobFinished(); + +private slots: + // called by menu action "Open Sandbox..." + void slotOpenSandbox(); + void slotSetupStatusBar(); + +protected: + virtual void guiActivateEvent(KParts::GUIActivateEvent* event); + +private: + enum JobType { Unknown, Commit }; + + void setupActions(); + + void readSettings(); + void writeSettings(); + + bool openSandbox(const QString &dirname); + void updateSandbox(const QString &extraopt = QString::null); + void addOrRemove(AddRemoveDialog::ActionType action); + void addOrRemoveWatch(WatchDialog::ActionType action); + void createOrDeleteTag(Cervisia::TagDialog::ActionType action); + void showJobStart(const QString &command); + void showDiff(const QString& revision); + void setFilter(); + + UpdateView *update; + ProtocolView *protocol; + bool hasRunningJob; + QSplitter *splitter; + + QString sandbox; + QString repository; + + QString changelogstr; + QStringList recentCommits; + bool opt_hideFiles, opt_hideUpToDate, opt_hideRemoved, opt_hideNotInCVS, opt_hideEmptyDirectories; + bool opt_createDirs, opt_pruneDirs; + bool opt_updateRecursive, opt_commitRecursive, opt_doCVSEdit; + + //for the Open Recent directories + KRecentFilesAction *recent; + + CvsService_stub* cvsService; + KParts::StatusBarExtension* m_statusBar; + CervisiaBrowserExtension* m_browserExt; + QLabel* filterLabel; + + int m_editWithId; + Cervisia::EditWithMenu* m_currentEditMenu; + JobType m_jobType; +}; + +typedef KParts::GenericFactory<CervisiaPart> CervisiaFactory; + +/** + * A mysterious class, needed to make Konqueror intrgration work. + */ +class CervisiaBrowserExtension : public KParts::BrowserExtension +{ + Q_OBJECT + +public: + CervisiaBrowserExtension( CervisiaPart * ); + ~CervisiaBrowserExtension(); + + void setPropertiesActionEnabled(bool enabled); + +public slots: + void properties(); +}; + +#endif // CERVISIAPART_H + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/cervisiapart.kcfg b/cervisia/cervisiapart.kcfg new file mode 100644 index 00000000..6dea38cf --- /dev/null +++ b/cervisia/cervisiapart.kcfg @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 + http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > + <kcfgfile name="cervisiapartrc" /> + <group name="Colors" > + <entry name="DiffChangeColor" key="DiffChange" type="Color"> + <default>#edbebe</default> + </entry> + <entry name="DiffDeleteColor" key="DiffDelete" type="Color"> + <default>#beedbe</default> + </entry> + <entry name="DiffInsertColor" key="DiffInsert" type="Color"> + <default>#bebeed</default> + </entry> + <entry name="LocalChangeColor" key="LocalChange" type="Color"> + <default>#8282ff</default> + </entry> + <entry name="RemoteChangeColor" key="RemoteChange" type="Color"> + <default>#46d246</default> + </entry> + <entry name="ConflictColor" key="Conflict" type="Color"> + <label>The foreground color used to highlight files with a conflict in the file view.</label> + <default>#ff8282</default> + </entry> + <entry name="NotInCvsColor" key="NotInCvsColor" type="Color"> + <default code="true">KGlobalSettings::textColor()</default> + </entry> + </group> + <group name="General" > + <entry key="Timeout" type="UInt"> + <label>Delay (ms) until the progress dialog appears.</label> + <default>4000</default> + </entry> + </group> +</kcfg> diff --git a/cervisia/cervisiasettings.kcfgc b/cervisia/cervisiasettings.kcfgc new file mode 100644 index 00000000..00c14ab5 --- /dev/null +++ b/cervisia/cervisiasettings.kcfgc @@ -0,0 +1,4 @@ +File=cervisiapart.kcfg +ClassName=CervisiaSettings +Singleton=true +Mutators=true diff --git a/cervisia/cervisiashell.cpp b/cervisia/cervisiashell.cpp new file mode 100644 index 00000000..33dac96c --- /dev/null +++ b/cervisia/cervisiashell.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "cervisiashell.h" + +#include <kapplication.h> +#include <kconfig.h> +#include <kedittoolbar.h> +#include <khelpmenu.h> +#include <kkeydialog.h> +#include <klibloader.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kstatusbar.h> +#include <kstdaction.h> +#include <kurl.h> + + +CervisiaShell::CervisiaShell( const char *name ) + : KParts::MainWindow( name ) + , m_part(0) +{ + setXMLFile( "cervisiashellui.rc" ); + + KLibFactory* factory = KLibLoader::self()->factory("libcervisiapart"); + if( factory ) + { + m_part = static_cast<KParts::ReadOnlyPart*>(factory->create(this, + "cervisiaview", "KParts::ReadOnlyPart")); + if( m_part ) + setCentralWidget(m_part->widget()); + } + else + { + KMessageBox::detailedError(this, i18n("The Cervisia library could not be loaded."), + KLibLoader::self()->lastErrorMessage()); + kapp->quit(); + return; + } + + setupActions(); + + // + // Magic needed for status texts + // + actionCollection()->setHighlightingEnabled(true); + connect( actionCollection(), SIGNAL( actionStatusText(const QString &) ), + statusBar(), SLOT( message(const QString &) ) ); + connect( actionCollection(), SIGNAL( clearStatusText() ), + statusBar(), SLOT( clear() ) ); + m_part->actionCollection()->setHighlightingEnabled(true); + connect( m_part->actionCollection(), SIGNAL( actionStatusText(const QString &) ), + statusBar(), SLOT( message(const QString &) ) ); + connect( m_part->actionCollection(), SIGNAL( clearStatusText() ), + statusBar(), SLOT( clear() ) ); + + createGUI( m_part ); + + // enable auto-save of toolbar/menubar/statusbar and window size settings + // and apply the previously saved settings + setAutoSaveSettings("MainWindow", true); + + // if the session is restoring, we already read the settings + if( !kapp->isRestored() ) + readSettings(); +} + +CervisiaShell::~CervisiaShell() +{ + delete m_part; +} + +void CervisiaShell::setupActions() +{ + setStandardToolBarMenuEnabled( true ); + + KAction *action = KStdAction::configureToolbars( this, SLOT(slotConfigureToolBars()), + actionCollection() ); + QString hint = i18n("Allows you to configure the toolbar"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = KStdAction::keyBindings( this, SLOT(slotConfigureKeys()), + actionCollection() ); + hint = i18n("Allows you to customize the keybindings"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = KStdAction::quit( kapp, SLOT( quit() ), actionCollection() ); + hint = i18n("Exits Cervisia"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + setHelpMenuEnabled(false); + (void) new KHelpMenu(this, instance()->aboutData(), false, actionCollection()); + + action = actionCollection()->action("help_contents"); + hint = i18n("Invokes the KDE help system with the Cervisia documentation"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = actionCollection()->action("help_report_bug"); + hint = i18n("Opens the bug report dialog"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = actionCollection()->action("help_about_app"); + hint = i18n("Displays the version number and copyright information"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = actionCollection()->action("help_about_kde"); + hint = i18n("Displays the information about KDE and its version number"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); +} + + +void CervisiaShell::openURL() +{ + if( !m_lastOpenDir.isEmpty() ) + m_part->openURL(KURL::fromPathOrURL(m_lastOpenDir)); +} + + +void CervisiaShell::openURL(const KURL& url) +{ + m_part->openURL(url); +} + + +void CervisiaShell::slotConfigureKeys() +{ + KKeyDialog dlg; + dlg.insert(actionCollection()); + if( m_part ) + dlg.insert(m_part->actionCollection()); + + dlg.configure(); +} + +void CervisiaShell::slotConfigureToolBars() +{ + saveMainWindowSettings( KGlobal::config(), autoSaveGroup() ); + KEditToolbar dlg( factory() ); + connect(&dlg,SIGNAL(newToolbarConfig()),this,SLOT(slotNewToolbarConfig())); + dlg.exec(); +} + +void CervisiaShell::slotNewToolbarConfig() +{ + applyMainWindowSettings( KGlobal::config(), autoSaveGroup() ); +} + +bool CervisiaShell::queryExit() +{ + writeSettings(); + return true; +} + + +void CervisiaShell::readProperties(KConfig* config) +{ + m_lastOpenDir = config->readPathEntry("Current Directory"); + + // if the session is restoring, make sure we open the URL + // since it's not handled by main() + if( kapp->isRestored() ) + openURL(); +} + + +void CervisiaShell::saveProperties(KConfig* config) +{ + // Save current working directory (if part was created) + if( m_part ) + { + config->writePathEntry("Current Directory", m_part->url().path()); + + // write to disk + config->sync(); + } +} + + +void CervisiaShell::readSettings() +{ + KConfig* config = KGlobal::config(); + config->setGroup("Session"); + + readProperties(config); +} + + +void CervisiaShell::writeSettings() +{ + KConfig* config = KGlobal::config(); + config->setGroup("Session"); + + saveProperties(config); +} + + +#include "cervisiashell.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/cervisiashell.h b/cervisia/cervisiashell.h new file mode 100644 index 00000000..6e19ce1d --- /dev/null +++ b/cervisia/cervisiashell.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef CERVISIASHELL_H +#define CERVISIASHELL_H + +#include <kparts/mainwindow.h> + +class KRecentFilesAction; + + +/** + * A basic shell that embeds the Cervisia part directly to make a standalone + * GUI available. + */ +class CervisiaShell : public KParts::MainWindow +{ + Q_OBJECT + +public: + CervisiaShell(const char* name=0); + virtual ~CervisiaShell(); + +public slots: + void openURL(); + void openURL(const KURL& url); + void slotConfigureKeys(); + void slotConfigureToolBars(); + +protected slots: + void slotNewToolbarConfig(); + +protected: + void setupActions(); + + bool queryExit(); + virtual void readProperties(KConfig* config); + virtual void saveProperties(KConfig* config); + +private: + void readSettings(); + void writeSettings(); + + KParts::ReadOnlyPart* m_part; + QString m_lastOpenDir; +}; + + +#endif // CERVISIASHELL_H + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/cervisiashellui.rc b/cervisia/cervisiashellui.rc new file mode 100644 index 00000000..4a8337c5 --- /dev/null +++ b/cervisia/cervisiashellui.rc @@ -0,0 +1,19 @@ +<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd"> +<kpartgui name="cervisia" version="4"> +<MenuBar> + <Menu name="file"><text>&File</text> + <Merge/> + </Menu> + <Menu name="settings"><text>&Settings</text> + <DefineGroup name="configure_merge_group" append="configure_merge" /> + <DefineGroup name="save_merge_group" append="show_merge" /> + </Menu> + <Menu name="help"><text>&Help</text> + <Merge/> + </Menu> + <Merge/> +</MenuBar> +<ToolBar name="mainToolBar"><text>Main Toolbar</text> +</ToolBar> +<StatusBar/> +</kpartgui> diff --git a/cervisia/cervisiaui.rc b/cervisia/cervisiaui.rc new file mode 100644 index 00000000..89fb5124 --- /dev/null +++ b/cervisia/cervisiaui.rc @@ -0,0 +1,179 @@ +<!DOCTYPE kpartgui> +<kpartgui name="cervisiapart" version="12"> +<MenuBar> + <Menu name="file"><text>&File</text> + <Action name="file_open"/> + <Action name="file_open_recent"/> + <Separator/> + <Action name="insert_changelog_entry"/> + <Separator/> + <Action name="file_update"/> + <Action name="file_status"/> + <Separator/> + <Action name="file_edit"/> + <Action name="file_resolve"/> + <Separator/> + <Action name="file_commit"/> + <Action name="file_add"/> + <Action name="file_add_binary"/> + <Action name="file_remove"/> + <Action name="file_revert_local_changes"/> + <Separator/> + </Menu> + <Menu name="view"><text>&View</text> + <Action name="stop_job"/> + <Separator/> + <Action name="view_log"/> + <Action name="view_annotate"/> + <Action name="view_diff_base"/> + <Action name="view_diff_head"/> + <Action name="view_last_change"/> + <Action name="view_history"/> + <Separator/> + <Action name="settings_hide_files"/> + <Action name="settings_hide_uptodate"/> + <Action name="settings_hide_removed"/> + <Action name="settings_hide_notincvs"/> + <Action name="settings_hide_empty_directories"/> + <Separator/> + <Action name="view_unfold_tree"/> + <Action name="view_fold_tree"/> + </Menu> + <Menu name="advanced"><text>&Advanced</text> + <Action name="create_tag"/> + <Action name="delete_tag"/> + <Action name="update_to_tag"/> + <Action name="update_to_head"/> + <Action name="merge"/> + <Separator/> + <Action name="add_watch"/> + <Action name="remove_watch"/> + <Action name="show_watchers"/> + <Separator/> + <Action name="edit_files"/> + <Action name="unedit_files"/> + <Action name="show_editors"/> + <Separator/> + <Action name="lock_files"/> + <Action name="unlock_files"/> + <Separator/> + <Action name="make_patch"/> + </Menu> + <Menu name="repository"><text>&Repository</text> + <Action name="repository_create"/> + <Action name="repository_checkout"/> + <Action name="repository_import"/> + <Separator/> + <Action name="show_repositories"/> + </Menu> + <Menu name="settings"><text>&Settings</text> + <Separator group="save_merge_group"/> + <Action name="settings_create_dirs" group="save_merge_group"/> + <Action name="settings_prune_dirs" group="save_merge_group"/> + <Action name="settings_update_recursively" group="save_merge_group"/> + <Action name="settings_commit_recursively" group="save_merge_group"/> + <Action name="settings_do_cvs_edit" group="save_merge_group"/> + <Action name="configure_cervisia" group="configure_merge_group"/> + <Separator group="save_merge_group"/> + </Menu> + <Menu name="help"><text>&Help</text> + <Action name="help_cvs_manual"/> + </Menu> +</MenuBar> +<ToolBar name="mainToolBar"><text>Main Toolbar</text> + <Action name="file_update"/> + <Action name="file_status"/> + <Separator lineSeparator="true"/> + <Action name="file_commit"/> + <Action name="file_add"/> + <Action name="file_remove"/> + <Separator lineSeparator="true"/> + <Action name="stop_job"/> +</ToolBar> +<Menu name="context_popup"> + <Action name="file_edit"/> + <Separator/> + <Action name="view_diff_base"/> + <Action name="view_diff_head"/> + <Action name="view_last_change"/> + <Action name="file_resolve"/> + <Action name="view_log"/> + <Separator/> + <Action name="file_update"/> + <Action name="file_commit"/> + <Separator/> + <Action name="file_add"/> + <Action name="file_remove"/> + <Separator/> + <Action name="file_properties"/> +</Menu> +<Menu name="folder_context_popup"> + <Action name="unfold_folder"/> + <Separator/> + <Action name="file_update"/> + <Action name="file_commit"/> + <Separator/> + <Action name="file_add"/> + <Action name="file_remove"/> +</Menu> +<State name="has_sandbox"> + <Enable> + <Action name="insert_changelog_entry"/> + <Action name="view_unfold_tree"/> + <Action name="view_fold_tree"/> + </Enable> +</State> +<State name="has_single_folder"> + <Enable> + <Action name="unfold_folder"/> + </Enable> +</State> +<State name="has_single_selection"> + <Enable> + <Action name="file_edit"/> + <Action name="file_resolve"/> + <Action name="view_log"/> + <Action name="view_annotate"/> + <Action name="view_diff_base"/> + <Action name="view_diff_head"/> + <Action name="view_last_change"/> + <Action name="file_properties"/> + </Enable> +</State> +<State name="item_selected"> + <Enable> + <Action name="view_history"/> + <Action name="make_patch"/> + </Enable> +</State> +<State name="has_no_job"> + <Enable> + <Action name="file_update"/> + <Action name="file_status"/> + <Action name="file_commit"/> + <Action name="file_add"/> + <Action name="file_add_binary"/> + <Action name="file_remove"/> + <Action name="file_revert_local_changes"/> + <Action name="create_tag"/> + <Action name="delete_tag"/> + <Action name="update_to_tag"/> + <Action name="update_to_head"/> + <Action name="merge"/> + <Action name="add_watch"/> + <Action name="remove_watch"/> + <Action name="show_watchers"/> + <Action name="edit_files"/> + <Action name="unedit_files"/> + <Action name="show_editors"/> + <Action name="lock_files"/> + <Action name="unlock_files"/> + </Enable> +</State> +<State name="has_running_job"> + <Disable> + <Action name="repository_checkout"/> + <Action name="repository_import"/> + </Disable> +</State> +</kpartgui> diff --git a/cervisia/change_colors.pl b/cervisia/change_colors.pl new file mode 100644 index 00000000..6f455251 --- /dev/null +++ b/cervisia/change_colors.pl @@ -0,0 +1,31 @@ +#!/usr/bin/perl + + +while(<>) +{ + ($key) = ($_ =~ /([^=]*)=(.*)$/); + ($value) = ($_ =~ /^[^=]*=(.*)$/); + + if( $key eq "Conflict" and $value eq "255,100,100" ) + { + print "# DELETE " . $key . "\n"; + print $key . "=255,130,130\n"; + next; + } + + if( $key eq "LocalChange" and $value eq "190,190,237" ) + { + print "# DELETE " . $key . "\n"; + print $key . "=130,130,255\n"; + next; + } + + if( $key eq "RemoteChange" and $value eq "255,240,190" ) + { + print "# DELETE " . $key . "\n"; + print $key . "=70,210,70\n"; + next; + } + + print $_; +} diff --git a/cervisia/changelogdlg.cpp b/cervisia/changelogdlg.cpp new file mode 100644 index 00000000..902a40f3 --- /dev/null +++ b/cervisia/changelogdlg.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2002-2003 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "changelogdlg.h" + +#include <qfile.h> +#include <qtextstream.h> +#include <kconfig.h> +#include <kglobalsettings.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <ktextedit.h> +#include "misc.h" + + +static inline QString DateStringISO8601() +{ + return QDate::currentDate().toString(Qt::ISODate); +} + + +ChangeLogDialog::Options *ChangeLogDialog::options = 0; + + +ChangeLogDialog::ChangeLogDialog(KConfig& cfg, QWidget *parent, const char *name) + : KDialogBase(parent, name, true, i18n("Edit ChangeLog"), + Ok | Cancel, Ok, true) + , partConfig(cfg) +{ + edit = new KTextEdit(this); + + cfg.setGroup("LookAndFeel"); + edit->setFont(cfg.readFontEntry("ChangeLogFont")); + + edit->setFocus(); + edit->setWordWrap(QTextEdit::NoWrap); + edit->setTextFormat(QTextEdit::PlainText); + edit->setCheckSpellingEnabled(true); + QFontMetrics const fm(edit->fontMetrics()); + edit->setMinimumSize(fm.width('0') * 80, + fm.lineSpacing() * 20); + + setMainWidget(edit); + + QSize size = configDialogSize(partConfig, "ChangeLogDialog"); + resize(size); +} + + +ChangeLogDialog::~ChangeLogDialog() +{ + saveDialogSize(partConfig, "ChangeLogDialog"); +} + + +void ChangeLogDialog::slotOk() +{ + // Write changelog + QFile f(fname); + if (!f.open(IO_ReadWrite)) + { + KMessageBox::sorry(this, + i18n("The ChangeLog file could not be written."), + "Cervisia"); + return; + } + + QTextStream stream(&f); + stream << edit->text(); + f.close(); + + KDialogBase::slotOk(); +} + + +bool ChangeLogDialog::readFile(const QString &filename) +{ + fname = filename; + + if (!QFile::exists(filename)) + { + if (KMessageBox::warningContinueCancel(this, + i18n("A ChangeLog file does not exist. Create one?"), + "Cervisia", + i18n("Create")) != KMessageBox::Continue) + return false; + } + else + { + QFile f(filename); + if (!f.open(IO_ReadWrite)) + { + KMessageBox::sorry(this, + i18n("The ChangeLog file could not be read."), + "Cervisia"); + return false; + } + QTextStream stream(&f); + edit->setText(stream.read()); + f.close(); + } + + KConfigGroupSaver cs(&partConfig, "General"); + const QString username = partConfig.readEntry("Username", Cervisia::UserName()); + + edit->insertParagraph("", 0); + edit->insertParagraph("\t* ", 0); + edit->insertParagraph("", 0); + edit->insertParagraph(DateStringISO8601() + " " + username, 0); + edit->setCursorPosition(2, 10); + + return true; +} + + +QString ChangeLogDialog::message() +{ + int no = 0; + // Find first line which begins with non-whitespace + while (no < edit->lines()) + { + QString str = edit->text(no); + if (!str.isEmpty() && !str[0].isSpace()) + break; + ++no; + } + ++no; + // Skip empty lines + while (no < edit->lines()) + { + QString str = edit->text(no); + if( str.isEmpty() || str == " " ) + break; + ++no; + } + QString res; + // Use all lines until one which begins with non-whitespace + // Remove tabs or 8 whitespace at beginning of each line + while (no < edit->lines()) + { + QString str = edit->text(no); + if (!str.isEmpty() && !str[0].isSpace()) + break; + if (!str.isEmpty() && str[0] == '\t') + str.remove(0, 1); + else + { + int j; + for (j = 0; j < (int)str.length(); ++j) + if (!str[j].isSpace()) + break; + str.remove(0, QMIN(j, 8)); + } + res += str; + res += '\n'; + ++no; + } + // Remove newlines at end + int l; + for (l = res.length()-1; l > 0; --l) + if (res[l] != '\n') + break; + res.truncate(l+1); + return res; +} + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/changelogdlg.h b/cervisia/changelogdlg.h new file mode 100644 index 00000000..6c1aa3e3 --- /dev/null +++ b/cervisia/changelogdlg.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 1999-2001 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2002-2003 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef CHANGELOGDLG_H +#define CHANGELOGDLG_H + +#include <kdialogbase.h> + +class KTextEdit; +class KConfig; + + +class ChangeLogDialog : public KDialogBase +{ +public: + explicit ChangeLogDialog( KConfig& cfg, QWidget *parent=0, const char *name=0 ); + + virtual ~ChangeLogDialog(); + + bool readFile(const QString &fileName); + QString message(); + +protected: + virtual void slotOk(); + +private: + struct Options { + QSize size; + }; + static Options *options; + + QString fname; + KTextEdit *edit; + KConfig& partConfig; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/checkoutdlg.cpp b/cervisia/checkoutdlg.cpp new file mode 100644 index 00000000..85a8b0a8 --- /dev/null +++ b/cervisia/checkoutdlg.cpp @@ -0,0 +1,484 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2003-2004 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "checkoutdlg.h" + +#include <qcheckbox.h> +#include <qcombobox.h> +#include <qdir.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qpushbutton.h> +#include <kprocess.h> +#include <kfiledialog.h> +#include <klineedit.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kurlcompletion.h> + +#include "progressdlg.h" +#include "repositories.h" +#include "misc.h" +#include "cvsservice_stub.h" + +using Cervisia::IsValidTag; + + +CheckoutDialog::CheckoutDialog(KConfig& cfg, CvsService_stub* service, + ActionType action, QWidget* parent, + const char* name) + : KDialogBase(parent, name, true, QString::null, + Ok | Cancel | Help, Ok, true) + , act(action) + , partConfig(cfg) + , cvsService(service) +{ + setCaption( (action==Checkout)? i18n("CVS Checkout") : i18n("CVS Import") ); + + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout* layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + QGridLayout* grid = new QGridLayout(layout); + grid->setColStretch(0, 1); + grid->setColStretch(1, 20); + for( int i = 0; i < ((action==Checkout)? 4 : 10); ++i ) + grid->setRowStretch(i, 0); + + repo_combo = new QComboBox(true, mainWidget); + repo_combo->setFocus(); + // make sure combobox is smaller than the screen + repo_combo->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + grid->addWidget(repo_combo, 0, 1); + + QLabel* repo_label = new QLabel(repo_combo, i18n("&Repository:"), mainWidget); + grid->addWidget(repo_label, 0, 0, AlignLeft | AlignVCenter); + + if( action == Import ) + { + module_edit = new KLineEdit(mainWidget); + grid->addWidget(module_edit, 1, 1); + QLabel* module_label = new QLabel(module_edit, i18n("&Module:"), mainWidget); + grid->addWidget(module_label, 1, 0, AlignLeft | AlignVCenter); + } + else + { + module_combo = new QComboBox(true, mainWidget); + + QPushButton* module_button = new QPushButton(i18n("Fetch &List"), mainWidget); + connect( module_button, SIGNAL(clicked()), + this, SLOT(moduleButtonClicked()) ); + + QBoxLayout* module_layout = new QHBoxLayout(); + grid->addLayout(module_layout, 1, 1); + module_layout->addWidget(module_combo, 10); + module_layout->addWidget(module_button, 0, AlignVCenter); + + QLabel* module_label = new QLabel(module_combo, i18n("&Module:"), mainWidget); + grid->addWidget(module_label, 1, 0, AlignLeft | AlignVCenter); + + branchCombo = new QComboBox(true, mainWidget); + + QPushButton* branchButton = new QPushButton(i18n("Fetch &List"), mainWidget); + connect( branchButton, SIGNAL(clicked()), + this, SLOT(branchButtonClicked()) ); + + QBoxLayout* branchLayout = new QHBoxLayout(); + grid->addLayout(branchLayout, 2, 1); + branchLayout->addWidget(branchCombo, 10); + branchLayout->addWidget(branchButton, 0, AlignVCenter); + + QLabel* branch_label = new QLabel(branchCombo, i18n("&Branch tag:"), + mainWidget); + grid->addWidget(branch_label, 2, 0, AlignLeft | AlignVCenter); + + connect( branchCombo, SIGNAL( textChanged( const QString&)), + this, SLOT( branchTextChanged() )); + + recursive_box = new QCheckBox(i18n("Re&cursive checkout"), mainWidget); + grid->addMultiCellWidget(recursive_box, 6, 6, 0, 1); + } + + workdir_edit = new KLineEdit(mainWidget); + workdir_edit->setText(QDir::homeDirPath()); + workdir_edit->setMinimumWidth(fontMetrics().width('X') * 40); + + KURLCompletion* comp = new KURLCompletion(); + workdir_edit->setCompletionObject(comp); + workdir_edit->setAutoDeleteCompletionObject(true); + connect( workdir_edit, SIGNAL(returnPressed(const QString&)), + comp, SLOT(addItem(const QString&)) ); + + QPushButton* dir_button = new QPushButton("...", mainWidget); + connect( dir_button, SIGNAL(clicked()), + this, SLOT(dirButtonClicked()) ); + dir_button->setFixedWidth(30); + + QBoxLayout* workdir_layout = new QHBoxLayout(); + grid->addLayout(workdir_layout, (action==Import)? 2 : 3, 1); + workdir_layout->addWidget(workdir_edit, 10); + workdir_layout->addWidget(dir_button, 0, AlignVCenter); + + QLabel* workdir_label = new QLabel(workdir_edit, i18n("Working &folder:"), + mainWidget); + grid->addWidget(workdir_label, (action==Import)? 2 : 3, 0, AlignLeft | AlignVCenter); + + if( action == Import ) + { + vendortag_edit = new KLineEdit(mainWidget); + grid->addWidget(vendortag_edit, 3, 1); + + QLabel* vendortag_label = new QLabel(vendortag_edit, i18n("&Vendor tag:"), + mainWidget); + grid->addWidget(vendortag_label, 3, 0, AlignLeft | AlignVCenter); + + releasetag_edit = new KLineEdit(mainWidget); + grid->addWidget(releasetag_edit, 4, 1); + + QLabel* releasetag_label = new QLabel(releasetag_edit, i18n("&Release tag:"), + mainWidget); + grid->addWidget(releasetag_label, 4, 0, AlignLeft | AlignVCenter); + + ignore_edit = new KLineEdit(mainWidget); + grid->addWidget(ignore_edit, 5, 1); + + QLabel* ignore_label = new QLabel(ignore_edit, i18n("&Ignore files:"), + mainWidget); + grid->addWidget(ignore_label, 5, 0, AlignLeft | AlignVCenter); + + comment_edit = new KLineEdit(mainWidget); + grid->addWidget(comment_edit, 6, 1); + + QLabel* comment_label = new QLabel(comment_edit, i18n("&Comment:"), + mainWidget); + grid->addWidget(comment_label, 6, 0, AlignLeft | AlignVCenter); + + binary_box = new QCheckBox(i18n("Import as &binaries"), mainWidget); + grid->addMultiCellWidget(binary_box, 7, 7, 0, 1); + + m_useModificationTimeBox = new QCheckBox( + i18n("Use file's modification time as time of import"), mainWidget); + grid->addMultiCellWidget(m_useModificationTimeBox, 8, 8, 0, 1); + } + else + { + alias_edit = new KLineEdit(mainWidget); + grid->addWidget(alias_edit, 4, 1); + + QLabel* alias_label = new QLabel(alias_edit, i18n("Chec&k out as:"), mainWidget); + grid->addWidget(alias_label, 4, 0, AlignLeft | AlignVCenter); + + export_box = new QCheckBox(i18n("Ex&port only"), mainWidget); + grid->addMultiCellWidget(export_box, 5, 5, 0, 1); + } + + QStringList list1 = Repositories::readCvsPassFile(); + QStringList::ConstIterator it1; + for (it1 = list1.begin(); it1 != list1.end(); ++it1) + repo_combo->insertItem(*it1); + + QStringList list2 = Repositories::readConfigFile(); + QStringList::ConstIterator it2; + for (it2 = list2.begin(); it2 != list2.end(); ++it2) + if (!list1.contains(*it2)) + repo_combo->insertItem(*it2); + + setHelp((act == Import) ? "importing" : "checkingout"); + + restoreUserInput(); +} + + +QString CheckoutDialog::workingDirectory() const +{ + return workdir_edit->text(); +} + + +QString CheckoutDialog::repository() const +{ + return repo_combo->currentText(); +} + + +QString CheckoutDialog::module() const +{ + return act==Import? module_edit->text() : module_combo->currentText(); +} + + +QString CheckoutDialog::branch() const +{ + return branchCombo->currentText(); +} + + +QString CheckoutDialog::vendorTag() const +{ + return vendortag_edit->text(); +} + + +QString CheckoutDialog::releaseTag() const +{ + return releasetag_edit->text(); +} + + +QString CheckoutDialog::ignoreFiles() const +{ + return ignore_edit->text(); +} + + +QString CheckoutDialog::comment() const +{ + return comment_edit->text(); +} + +QString CheckoutDialog::alias() const +{ + return alias_edit->text(); +} + +bool CheckoutDialog::importBinary() const +{ + return binary_box->isChecked(); +} + +bool CheckoutDialog::useModificationTime() const +{ + return m_useModificationTimeBox->isChecked(); +} + +bool CheckoutDialog::exportOnly() const +{ + if( export_box->isEnabled() ) + return export_box->isChecked(); + + return false; +} + +bool CheckoutDialog::recursive() const +{ + return recursive_box->isChecked(); +} + +void CheckoutDialog::slotOk() +{ + QFileInfo fi(workingDirectory()); + if (!fi.exists() || !fi.isDir()) + { + KMessageBox::information(this, i18n("Please choose an existing working folder.")); + return; + } + if (module().isEmpty()) + { + KMessageBox::information(this, i18n("Please specify a module name.")); + return; + } + + if (act==Import) + { + if (vendorTag().isEmpty() || releaseTag().isEmpty()) + { + KMessageBox::information(this, + i18n("Please specify a vendor tag and a release tag.")); + return; + } + if (!IsValidTag(vendorTag()) || !IsValidTag(releaseTag())) + { + KMessageBox::information(this, + i18n("Tags must start with a letter and may contain\n" + "letters, digits and the characters '-' and '_'.")); + return; + } + } + else + { + if( branch().isEmpty() && exportOnly() ) + { + KMessageBox::information(this, + i18n("A branch must be specified for export.")); + return; + } + } + + saveUserInput(); + + KDialogBase::slotOk(); +} + + +void CheckoutDialog::dirButtonClicked() +{ + QString dir = KFileDialog::getExistingDirectory(workdir_edit->text()); + if (!dir.isEmpty()) + workdir_edit->setText(dir); +} + + +void CheckoutDialog::moduleButtonClicked() +{ + DCOPRef cvsJob = cvsService->moduleList(repository()); + if( !cvsService->ok() ) + return; + + ProgressDialog dlg(this, "Checkout", cvsJob, "checkout", i18n("CVS Checkout")); + if( !dlg.execute() ) + return; + + module_combo->clear(); + QString str; + while (dlg.getLine(str)) + { + if (str.left(12) == "Unknown host") + continue; + + int pos = str.find(' '); + if (pos == -1) + pos = str.find('\t'); + if (pos == -1) + pos = str.length(); + QString module( str.left(pos).stripWhiteSpace() ); + if ( !module.isEmpty() ) + module_combo->insertItem(module); + } +} + + +void CheckoutDialog::branchButtonClicked() +{ + QStringList branchTagList; + + if( repository().isEmpty() ) + { + KMessageBox::information(this, i18n("Please specify a repository.")); + return; + } + + if( module().isEmpty() ) + { + KMessageBox::information(this, i18n("Please specify a module name.")); + return; + } + + DCOPRef cvsJob = cvsService->rlog(repository(), module(), + false/*recursive*/); + if( !cvsService->ok() ) + return; + + ProgressDialog dlg(this, "Remote Log", cvsJob, QString::null, + i18n("CVS Remote Log")); + if( !dlg.execute() ) + return; + + QString line; + while( dlg.getLine(line) ) + { + int colonPos; + + if( line.isEmpty() || line[0] != '\t' ) + continue; + if( (colonPos = line.find(':', 1)) < 0 ) + continue; + + const QString tag = line.mid(1, colonPos - 1); + if( !branchTagList.contains(tag) ) + branchTagList.push_back(tag); + } + + branchTagList.sort(); + + branchCombo->clear(); + branchCombo->insertStringList(branchTagList); +} + + +void CheckoutDialog::restoreUserInput() +{ + KConfigGroupSaver cs(&partConfig, "CheckoutDialog"); + + repo_combo->setEditText(partConfig.readEntry("Repository")); + workdir_edit->setText(partConfig.readPathEntry("Working directory")); + + if (act == Import) + { + module_edit->setText(partConfig.readEntry("Module")); + vendortag_edit->setText(partConfig.readEntry("Vendor tag")); + releasetag_edit->setText(partConfig.readEntry("Release tag")); + ignore_edit->setText(partConfig.readEntry("Ignore files")); + binary_box->setChecked(partConfig.readBoolEntry("Import binary")); + } + else + { + module_combo->setEditText(partConfig.readEntry("Module")); + branchCombo->setCurrentText(partConfig.readEntry("Branch")); + alias_edit->setText(partConfig.readEntry("Alias")); + export_box->setChecked(partConfig.readBoolEntry("ExportOnly")); + recursive_box->setChecked(true); + } +} + + +void CheckoutDialog::saveUserInput() +{ + KConfigGroupSaver cs(&partConfig, "CheckoutDialog"); + + partConfig.writeEntry("Repository", repository()); + partConfig.writeEntry("Module", module()); + partConfig.writeEntry("Working directory", workingDirectory()); + + if (act == Import) + { + partConfig.writeEntry("Vendor tag", vendorTag()); + partConfig.writeEntry("Release tag", releaseTag()); + partConfig.writeEntry("Ignore files", ignoreFiles()); + partConfig.writeEntry("Import binary", importBinary()); + } + else + { + partConfig.writeEntry("Branch", branch()); + partConfig.writeEntry("Alias", alias()); + partConfig.writeEntry("ExportOnly", exportOnly()); + } +} + +void CheckoutDialog::branchTextChanged() +{ + if( branch().isEmpty() ) + { + export_box->setEnabled(false); + export_box->setChecked(false); + } + else + { + export_box->setEnabled(true); + } +} + + +#include "checkoutdlg.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/checkoutdlg.h b/cervisia/checkoutdlg.h new file mode 100644 index 00000000..33f95f2b --- /dev/null +++ b/cervisia/checkoutdlg.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef CHECKOUTDLG_H +#define CHECKOUTDLG_H + + +#include <kdialogbase.h> + + +class QCheckBox; +class QComboBox; +class KConfig; +class KLineEdit; +class CvsService_stub; + + +class CheckoutDialog : public KDialogBase +{ + Q_OBJECT + +public: + enum ActionType { Checkout, Import }; + + CheckoutDialog( KConfig& cfg, CvsService_stub* service, ActionType action, + QWidget *parent=0, const char *name=0 ); + + QString workingDirectory() const; + QString repository() const; + QString module() const; + QString branch() const; + QString vendorTag() const; + QString releaseTag() const; + QString ignoreFiles() const; + QString comment() const; + QString alias() const; + bool importBinary() const; + bool useModificationTime() const; + bool exportOnly() const; + bool recursive() const; + +protected: + virtual void slotOk(); + +private slots: + void dirButtonClicked(); + void moduleButtonClicked(); + void branchButtonClicked(); + void branchTextChanged(); + +private: + void saveUserInput(); + void restoreUserInput(); + + QComboBox *repo_combo, *module_combo, *branchCombo; + KLineEdit *module_edit, *workdir_edit; + KLineEdit *comment_edit; + KLineEdit *vendortag_edit, *releasetag_edit, *ignore_edit, *alias_edit; + QCheckBox *binary_box, *export_box, *recursive_box; + QCheckBox* m_useModificationTimeBox; + ActionType act; + KConfig& partConfig; + + CvsService_stub *cvsService; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/commitdlg.cpp b/cervisia/commitdlg.cpp new file mode 100644 index 00000000..53c91ba7 --- /dev/null +++ b/cervisia/commitdlg.cpp @@ -0,0 +1,326 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2002-2005 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "commitdlg.h" + +#include <qcombobox.h> +#include <qcheckbox.h> +#include <qdir.h> +#include <qfileinfo.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qheader.h> +#include <klistview.h> +#include <kconfig.h> +#include <klocale.h> + +#include "cvsservice_stub.h" +#include "logmessageedit.h" +#include "diffdlg.h" + + +class CommitListItem : public QCheckListItem +{ +public: + CommitListItem(QListView* parent, const QString& text, const QString fileName) + : QCheckListItem(parent, text, QCheckListItem::CheckBox) + , m_fileName(fileName) + { + } + + QString fileName() const { return m_fileName; } + +private: + QString m_fileName; +}; + + +CommitDialog::CommitDialog(KConfig& cfg, CvsService_stub* service, + QWidget *parent, const char *name) + : KDialogBase(parent, name, true, i18n("CVS Commit"), + Ok | Cancel | Help | User1, Ok, true) + , partConfig(cfg) + , cvsService(service) +{ + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout *layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + QLabel *textlabel = new QLabel( i18n("Commit the following &files:"), mainWidget ); + layout->addWidget(textlabel); + + m_fileList = new KListView(mainWidget); + m_fileList->addColumn(""); + m_fileList->setFullWidth(true); + m_fileList->header()->hide(); + textlabel->setBuddy(m_fileList); + connect( m_fileList, SIGNAL(doubleClicked(QListViewItem*)), + this, SLOT(fileSelected(QListViewItem*))); + connect( m_fileList, SIGNAL(selectionChanged()), + this, SLOT(fileHighlighted()) ); + layout->addWidget(m_fileList, 5); + + QLabel *archivelabel = new QLabel(i18n("Older &messages:"), mainWidget); + layout->addWidget(archivelabel); + + combo = new QComboBox(mainWidget); + archivelabel->setBuddy(combo); + connect( combo, SIGNAL(activated(int)), this, SLOT(comboActivated(int)) ); + // make sure that combobox is smaller than the screen + combo->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); + layout->addWidget(combo); + + QLabel *messagelabel = new QLabel(i18n("&Log message:"), mainWidget); + layout->addWidget(messagelabel); + + edit = new Cervisia::LogMessageEdit(mainWidget); + messagelabel->setBuddy(edit); + edit->setCheckSpellingEnabled(true); + edit->setFocus(); + edit->setMinimumSize(400, 100); + layout->addWidget(edit, 10); + + m_useTemplateChk = new QCheckBox(i18n("Use log message &template"), mainWidget); + layout->addWidget(m_useTemplateChk); + connect( m_useTemplateChk, SIGNAL(clicked()), this, SLOT(useTemplateClicked()) ); + + checkForTemplateFile(); + + setButtonGuiItem(User1, KGuiItem(i18n("&Diff"), "vcs_diff")); + enableButton(User1, false); + connect( this, SIGNAL(user1Clicked()), + this, SLOT(diffClicked()) ); + + setHelp("commitingfiles"); + + QSize size = configDialogSize(partConfig, "CommitDialog"); + resize(size); +} + + +CommitDialog::~CommitDialog() +{ + saveDialogSize(partConfig, "CommitDialog"); + + KConfigGroupSaver cs(&partConfig, "CommitDialog"); + partConfig.writeEntry("UseTemplate", m_useTemplateChk->isChecked()); +} + + +void CommitDialog::setFileList(const QStringList &list) +{ + QString currentDirName = QFileInfo(QChar('.')).absFilePath(); + + QStringList::ConstIterator it = list.begin(); + for( ; it != list.end(); ++it ) + { + // the dot for the root directory is hard to see, so + // we convert it to the absolut path + QString text = (*it != "." ? *it : currentDirName); + + edit->compObj()->addItem(text); + CommitListItem* item = new CommitListItem(m_fileList, text, *it); + item->setOn(true); + } +} + + +QStringList CommitDialog::fileList() const +{ + QStringList files; + + QListViewItemIterator it(m_fileList, QListViewItemIterator::Checked); + for( ; it.current(); ++it ) + { + CommitListItem* item = static_cast<CommitListItem*>(it.current()); + files.append(item->fileName()); + } + + return files; +} + + +void CommitDialog::setLogMessage(const QString &msg) +{ + edit->setText(msg); + + if( m_useTemplateChk->isChecked() ) + addTemplateText(); +} + + +QString CommitDialog::logMessage() const +{ + return edit->text(); +} + + +void CommitDialog::setLogHistory(const QStringList &list) +{ + commits = list; + + combo->insertItem(i18n("Current")); + + for ( QStringList::ConstIterator it = list.begin(); + it != list.end(); ++it ) + { + if( (*it).isEmpty() ) + continue; + + QString txt = *it; + int index = txt.find('\n', 0); + if ( index != -1 ) // Fetch first line + { + txt = txt.mid(0, index); + txt += "..."; + } + + combo->insertItem(txt); + } +} + + +void CommitDialog::comboActivated(int index) +{ + if (index == current_index) + return; + + if (index == 0) // Handle current text + edit->setText(current_text); + else + { + if (current_index == 0) // Store current text + current_text = edit->text(); + + // Show archived text + edit->setText(commits[index-1]); + } + current_index = index; +} + + +void CommitDialog::fileSelected(QListViewItem* item) +{ + // double click on empty space? + if( !item ) + return; + + showDiffDialog(item->text(0)); +} + + +void CommitDialog::fileHighlighted() +{ + bool isItemSelected = (m_fileList->selectedItem() != 0); + enableButton(User1, isItemSelected); +} + + +void CommitDialog::diffClicked() +{ + QListViewItem* item = m_fileList->selectedItem(); + if( !item ) + return; + + showDiffDialog(item->text(0)); +} + + +void CommitDialog::showDiffDialog(const QString& fileName) +{ + DiffDialog *l = new DiffDialog(partConfig, this, "diffdialog"); + + // disable diff button so user doesn't open the same diff several times (#83018) + enableButton(User1, false); + + if (l->parseCvsDiff(cvsService, fileName, "", "")) + l->show(); + else + delete l; + + // re-enable diff button + enableButton(User1, true); +} + + +void CommitDialog::useTemplateClicked() +{ + if( m_useTemplateChk->isChecked() ) + { + addTemplateText(); + } + else + { + removeTemplateText(); + } +} + + +void CommitDialog::checkForTemplateFile() +{ + QString filename = QDir::current().absPath() + "/CVS/Template"; + if( QFile::exists(filename) ) + { + QFile f(filename); + if( f.open(IO_ReadOnly) ) + { + QTextStream stream(&f); + m_templateText = stream.read(); + f.close(); + + m_useTemplateChk->setEnabled(true); + KConfigGroupSaver cs(&partConfig, "CommitDialog"); + bool check = partConfig.readBoolEntry("UseTemplate", true); + m_useTemplateChk->setChecked(check); + + addTemplateText(); + } + else + { + m_useTemplateChk->setEnabled(false); + } + } + else + { + m_useTemplateChk->setEnabled(false); + } +} + + +void CommitDialog::addTemplateText() +{ + edit->append(m_templateText); + edit->moveCursor(QTextEdit::MoveHome, false); + edit->ensureCursorVisible(); +} + + +void CommitDialog::removeTemplateText() +{ + edit->setText(edit->text().remove(m_templateText)); +} + + +#include "commitdlg.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/commitdlg.h b/cervisia/commitdlg.h new file mode 100644 index 00000000..fe3d9acf --- /dev/null +++ b/cervisia/commitdlg.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2003-2005 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef COMMITDLG_H +#define COMMITDLG_H + +#include <qstringlist.h> +#include <kdialogbase.h> + +namespace Cervisia { class LogMessageEdit; } + +class QComboBox; +class QCheckBox; +class KListView; +class KConfig; +class CvsService_stub; + + +class CommitDialog : public KDialogBase +{ + Q_OBJECT + +public: + CommitDialog( KConfig& cfg, CvsService_stub* service, QWidget *parent=0, + const char *name=0 ); + + virtual ~CommitDialog(); + + void setFileList(const QStringList &list); + QStringList fileList() const; + void setLogMessage(const QString &msg); + QString logMessage() const; + void setLogHistory(const QStringList &list); + +private slots: + void comboActivated(int); + void fileSelected(QListViewItem* item); + void fileHighlighted(); + void diffClicked(); + void useTemplateClicked(); + +private: + void showDiffDialog(const QString& fileName); + void checkForTemplateFile(); + void addTemplateText(); + void removeTemplateText(); + + KListView* m_fileList; + Cervisia::LogMessageEdit* edit; + QComboBox *combo; + QStringList commits; + int current_index; + QString current_text; + int highlightedFile; + + QCheckBox* m_useTemplateChk; + QString m_templateText; + + KConfig& partConfig; + CvsService_stub* cvsService; // for diff dialog +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/cvsdir.cpp b/cervisia/cvsdir.cpp new file mode 100644 index 00000000..263fd704 --- /dev/null +++ b/cervisia/cvsdir.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "cvsdir.h" + +#include "dirignorelist.h" +#include "globalignorelist.h" +using namespace Cervisia; + + +CvsDir::CvsDir(const QString &path) + : QDir( path, 0, QDir::Name, + QDir::All | QDir::Hidden | QDir::NoSymLinks ) +{} + + +const QFileInfoList *CvsDir::entryInfoList() const +{ + DirIgnoreList ignorelist(absPath()); + const QFileInfoList *fulllist = QDir::entryInfoList(); + if (!fulllist) + return 0; + + entrylist.clear(); + + QFileInfoListIterator it(*fulllist); + for (; it.current(); ++it) + { + if (!ignorelist.matches(it.current()) && !GlobalIgnoreList().matches(it.current())) + entrylist.append(it.current()); + } + + return &entrylist; +} + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/cvsdir.h b/cervisia/cvsdir.h new file mode 100644 index 00000000..8f732163 --- /dev/null +++ b/cervisia/cvsdir.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef CVSDIR_H +#define CVSDIR_H + +#include <qdir.h> + + +class CvsDir : public QDir +{ +public: + explicit CvsDir(const QString &path); + + const QFileInfoList *entryInfoList() const; + +private: + mutable QFileInfoList entrylist; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: + diff --git a/cervisia/cvsinitdlg.cpp b/cervisia/cvsinitdlg.cpp new file mode 100644 index 00000000..9de80759 --- /dev/null +++ b/cervisia/cvsinitdlg.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "cvsinitdlg.h" + +#include <qlabel.h> +#include <qlayout.h> +#include <qpushbutton.h> + +#include <kfiledialog.h> +#include <klineedit.h> +#include <klocale.h> +#include <kurlcompletion.h> + + +using Cervisia::CvsInitDialog; + + +CvsInitDialog::CvsInitDialog(QWidget* parent, const char* name) + : KDialogBase(parent, name, true, i18n("Create New Repository (cvs init)"), + Ok | Cancel, Ok, true) +{ + QFrame* mainWidget = makeMainWidget(); + QVBoxLayout* mainLayout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + QLabel* dirLabel = new QLabel(i18n("Repository folder:"), mainWidget); + mainLayout->addWidget(dirLabel); + + QHBoxLayout* dirLayout = new QHBoxLayout(mainLayout); + + m_directoryEdit = new KLineEdit(mainWidget); + m_directoryEdit->setFocus(); + + KURLCompletion* comp = new KURLCompletion(); + m_directoryEdit->setCompletionObject(comp); + m_directoryEdit->setAutoDeleteCompletionObject(true); + + dirLabel->setBuddy(m_directoryEdit); + dirLayout->addWidget(m_directoryEdit); + + QPushButton* dirButton = new QPushButton("...", mainWidget); + dirButton->setFixedWidth(30); + dirLayout->addWidget(dirButton); + + connect( dirButton, SIGNAL(clicked()), + this, SLOT(dirButtonClicked()) ); + connect( m_directoryEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(lineEditTextChanged(const QString&))); + + enableButton(Ok, false); + + setMinimumWidth(350); +} + + +QString CvsInitDialog::directory() const +{ + return m_directoryEdit->text(); +} + + +void CvsInitDialog::dirButtonClicked() +{ + QString dir = KFileDialog::getExistingDirectory(m_directoryEdit->text()); + if( !dir.isEmpty() ) + m_directoryEdit->setText(dir); +} + + +void CvsInitDialog::lineEditTextChanged(const QString& text) +{ + enableButton(Ok, !text.stripWhiteSpace().isEmpty()); +} + +#include "cvsinitdlg.moc" diff --git a/cervisia/cvsinitdlg.h b/cervisia/cvsinitdlg.h new file mode 100644 index 00000000..007617a7 --- /dev/null +++ b/cervisia/cvsinitdlg.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef CERVISIA_CVSINITDLG_H +#define CERVISIA_CVSINITDLG_H + +#include <kdialogbase.h> + +class KLineEdit; + + +namespace Cervisia +{ + + +class CvsInitDialog : public KDialogBase +{ + Q_OBJECT + +public: + CvsInitDialog(QWidget* parent = 0, const char* name = 0); + + QString directory() const; + +private slots: + void dirButtonClicked(); + void lineEditTextChanged(const QString& text); + +private: + KLineEdit* m_directoryEdit; +}; + + +} + +#endif diff --git a/cervisia/cvsservice/DESIGN b/cervisia/cvsservice/DESIGN new file mode 100644 index 00000000..cbd414da --- /dev/null +++ b/cervisia/cvsservice/DESIGN @@ -0,0 +1,108 @@ +OVERVIEW +-------- + +The cvs DCOP service consists of the following three parts: + +1. CvsService - The main interface to the functionality of the cvs command line + client. There is one method for each cvs command, e.g. add, + checkout, commit, etc... The methods assemble the command line + arguments, create a CvsJob and return a DCOPRef object for it + to the caller. There is one instance of this service for each + application instance. + +2. Repository - This DCOPObject manages the configuration data of the current + cvs repository. The data is automatically updated when other + service instances change it. + +3. CvsJob - This class represents a cvs job. You can execute and cancel it, + and you can retrieve the output of the cvs client by either + connecting to the proper DCOP signals or by using the output() + method. There are two types of jobs. First the non-concurrent + job which has to run alone, like cvs update or import. Second + the jobs which can run concurrently like cvs log or annotate. + +USAGE +----- + +How-to use this service in C++ applications: + + // start DCOP service + QString error; + QCString appId; + + KApplication::startServiceByDesktopName("cvsservice", QStringList(), &error, + &appId); + + // create stub for repository + Repository_stub repository(appId, "CvsRepository"); + + // set directory of working copy + repository.setWorkingCopy("/home/user/kde/kdesdk/cervisia"); + + // create stub for service + CvsService_stub cvsService(appId, "CvsService"); + + // call "cvs log" for cervisiapart.h + DCOPRef job = cvsService.log("cervisiapart.h"); + + // connect to signals to get output + connectDCOPSignal(job.app(), job.obj(), "jobExited(bool, int)", [MY SLOT]); + connectDCOPSignal(job.app(), job.obj(), "receivedStdout(QString)", + [MY SLOT]); + + // execute the cvs command + job.execute(); + + +How-to use this service in a shell script: + + #!/bin/sh + + # start DCOP service + APP=`dcopstart cvsservice` + + # set directory of working copy + dcop $APP CvsRepository setWorkingCopy /home/user/kde/kdesdk/cervisia + + # call "cvs log" for cervisiapart.h + JOB=`dcop $APP CvsService log cervisiapart.h` + + # execute the cvs command + dcop $JOB execute + + # print the output on stdout + dcop $JOB output + + # stop DCOP service + dcop $APP CvsService quit + + +How-to use this service in a javascript: + + #!/usr/bin/env kjscmd + + var client = new DCOPClient(this); + if ( client.attach() ) + { + // start DCOP service + var appID = client.dcopStart("cvsservice"); + + // set directory of working copy + client.send(appID, "CvsRepository", "setWorkingCopy(QString)", "/home/user/kde/kdesdk/cervisia"); + + // call "cvs log" for cervisiapart.h + var job = client.call(appID, "CvsService", "log(QString)", "cervisiapart.h"); + + // execute the cvs command + job.call("execute()"); + + // wait for job to finish + while( job.call("isRunning()") ); + + // print the output on stdout + var output = job.call("output()"); + println(output); + + // stop DCOP service + client.send(appID, "CvsService", "quit()"); + } diff --git a/cervisia/cvsservice/Makefile.am b/cervisia/cvsservice/Makefile.am new file mode 100644 index 00000000..7b036036 --- /dev/null +++ b/cervisia/cvsservice/Makefile.am @@ -0,0 +1,54 @@ +# +# CvsService Makefile.am +# +# Copyright (c) 2002-2003 Christian Loose <christian.loose@hamburg.de> +# +# + +INCLUDES = $(all_includes) + +# cvs DCOP service +bin_PROGRAMS = +kdeinit_LTLIBRARIES = cvsservice.la cvsaskpass.la + +cvsservice_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module +cvsservice_la_LIBADD = $(LIB_KIO) +cvsservice_la_SOURCES = main.cpp cvsservice.cpp cvsjob.cpp \ + cvsservice.skel cvsservice.stub cvsjob.skel cvsjob.stub \ + repository.cpp repository.skel repository.stub sshagent.cpp \ + cvsserviceutils.cpp cvsloginjob.cpp cvsloginjob.skel cvsloginjob.stub + +cvsaskpass_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module +cvsaskpass_la_LIBADD = $(LIB_KDEUI) +cvsaskpass_la_SOURCES = cvsaskpass.cpp + +include_HEADERS = cvsservice_stub.h cvsjob_stub.h repository_stub.h +noinst_HEADERS = cvsservice.h cvsjob.h repository.h cvsserviceutils.h \ + sshagent.h + +# cvs DCOP service stub library +lib_LTLIBRARIES = libcvsservice.la + +libcvsservice_la_LDFLAGS = $(all_libraries) -no-undefined -version-info 0:1 +libcvsservice_la_LIBADD = $(LIB_KDECORE) +libcvsservice_la_SOURCES = cvsservice.stub cvsjob.stub repository.stub dummy.cpp + +dummy.cpp: + echo > dummy.cpp + +# let automoc handle all of the meta source files (moc) +METASOURCES = AUTO + +# install .desktop file +service_DATA = cvsservice.desktop +servicedir = $(kde_servicesdir) + +# i18n +messages: + $(XGETTEXT) *.cpp *.h -o $(podir)/cvsservice.pot + +# API documentation +# Not activated because KDE_INIT_DOXYGEN is missing in +# kdesdk's configure.in.in +#DOXYGEN_REFERENCES = dcop kdecore kdeui +#include ../../admin/Doxyfile.am diff --git a/cervisia/cvsservice/TODO b/cervisia/cvsservice/TODO new file mode 100644 index 00000000..d46d2f9a --- /dev/null +++ b/cervisia/cvsservice/TODO @@ -0,0 +1,12 @@ +TODO +---- + +* Move handling of recent commit messages from CervisiaPart to + DCOP service + +* Add special job classes derived from CvsJob that take over + the parsing of cvs' output from Cervisia + +* Missing cvs functionality needed for Cervisia: + - cvs watch (add/remove) + - cvs diff (make patch) diff --git a/cervisia/cvsservice/cvsaskpass.cpp b/cervisia/cvsservice/cvsaskpass.cpp new file mode 100644 index 00000000..c5410dee --- /dev/null +++ b/cervisia/cvsservice/cvsaskpass.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include <qregexp.h> +#include <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <klocale.h> +#include <kpassdlg.h> + +#include <iostream> + + +static KCmdLineOptions options[] = +{ + { "+[prompt]", I18N_NOOP("prompt"), 0 }, + KCmdLineLastOption +}; + + +extern "C" KDE_EXPORT int kdemain(int argc, char** argv) +{ + KAboutData about("cvsaskpass", I18N_NOOP("cvsaskpass"), "0.1", + I18N_NOOP("ssh-askpass for the CVS DCOP Service"), + KAboutData::License_LGPL, + I18N_NOOP("Copyright (c) 2003 Christian Loose")); + + KCmdLineArgs::init(argc, argv, &about); + KCmdLineArgs::addCmdLineOptions(options); + + // no need to register with the dcop server + KApplication::disableAutoDcopRegistration(); + KApplication app; + + // no need for session management + app.disableSessionManagement(); + + if( !KCmdLineArgs::parsedArgs()->count() ) + return 1; + + // parse repository name from the passed argument + QString prompt = KCmdLineArgs::parsedArgs()->arg(0); + QRegExp rx("(.*@.*)'s password:"); + int pos = rx.search(prompt); + + KPasswordDialog dlg(KPasswordDialog::Password, false, 0); + dlg.setPrompt(i18n("Please type in your password below.")); + + if( pos > -1 ) + dlg.addLine(i18n("Repository:"), rx.cap(1)); + + int res = dlg.exec(); + if( res == KPasswordDialog::Accepted ) + { + std::cout << dlg.password() << std::endl; + return 0; + } + + return 1; +} diff --git a/cervisia/cvsservice/cvsjob.cpp b/cervisia/cvsservice/cvsjob.cpp new file mode 100644 index 00000000..1178de96 --- /dev/null +++ b/cervisia/cvsservice/cvsjob.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2002-2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "cvsjob.h" + +#include <qfile.h> +#include <kdebug.h> +#include <kprocess.h> + +#include "sshagent.h" + + +struct CvsJob::Private +{ + Private() : isRunning(false) + { + childproc = new KProcess; + childproc->setUseShell(true, "/bin/sh"); + } + ~Private() { delete childproc; } + + KProcess* childproc; + QString server; + QString rsh; + QString directory; + bool isRunning; + QStringList outputLines; +}; + + +CvsJob::CvsJob(unsigned jobNum) + : QObject() + , DCOPObject() + , d(new Private) +{ + QString objId("CvsJob" + QString::number(jobNum)); + setObjId(objId.local8Bit()); +} + + +CvsJob::CvsJob(const QString& objId) + : QObject() + , DCOPObject() + , d(new Private) +{ + setObjId(objId.local8Bit()); +} + + +CvsJob::~CvsJob() +{ + delete d; +} + + +void CvsJob::clearCvsCommand() +{ + d->childproc->clearArguments(); +} + + +void CvsJob::setRSH(const QString& rsh) +{ + d->rsh = rsh; +} + + +void CvsJob::setServer(const QString& server) +{ + d->server = server; +} + + +void CvsJob::setDirectory(const QString& directory) +{ + d->directory = directory; +} + + +bool CvsJob::isRunning() const +{ + return d->isRunning; +} + + +CvsJob& CvsJob::operator<<(const QString& arg) +{ + *d->childproc << arg; + return *this; +} + + +CvsJob& CvsJob::operator<<(const char* arg) +{ + *d->childproc << arg; + return *this; +} + + +CvsJob& CvsJob::operator<<(const QCString& arg) +{ + *d->childproc << arg; + return *this; +} + + +CvsJob& CvsJob::operator<<(const QStringList& args) +{ + *d->childproc << args; + return *this; +} + + +QString CvsJob::cvsCommand() const +{ + QString command; + + const QValueList<QCString>& args(d->childproc->args()); + for (QValueList<QCString>::const_iterator it = args.begin(), itEnd = args.end(); + it != itEnd; ++it) + { + if (!command.isEmpty()) + command += ' '; + + command += QFile::decodeName(*it); + } + + return command; +} + + +QStringList CvsJob::output() const +{ + return d->outputLines; +} + + +bool CvsJob::execute() +{ + // setup job environment to use the ssh-agent (if it is running) + SshAgent ssh; + if( !ssh.pid().isEmpty() ) + { + // kdDebug(8051) << "PID = " << ssh.pid() << endl; + // kdDebug(8051) << "SOCK = " << ssh.authSock() << endl; + + d->childproc->setEnvironment("SSH_AGENT_PID", ssh.pid()); + d->childproc->setEnvironment("SSH_AUTH_SOCK", ssh.authSock()); + } + + d->childproc->setEnvironment("SSH_ASKPASS", "cvsaskpass"); + + if( !d->rsh.isEmpty() ) + d->childproc->setEnvironment("CVS_RSH", d->rsh); + + if( !d->server.isEmpty() ) + d->childproc->setEnvironment("CVS_SERVER", d->server); + + if( !d->directory.isEmpty() ) + d->childproc->setWorkingDirectory(d->directory); + + connect(d->childproc, SIGNAL(processExited(KProcess*)), + SLOT(slotProcessExited())); + connect(d->childproc, SIGNAL(receivedStdout(KProcess*, char*, int)), + SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(d->childproc, SIGNAL(receivedStderr(KProcess*, char*, int)), + SLOT(slotReceivedStderr(KProcess*, char*, int)) ); + + kdDebug(8051) << "Execute cvs command: " << cvsCommand() << endl; + + d->isRunning = true; + return d->childproc->start(KProcess::NotifyOnExit, KProcess::AllOutput); +} + + +void CvsJob::cancel() +{ + d->childproc->kill(); +} + + +void CvsJob::slotProcessExited() +{ + // disconnect all connections to childproc's signals + d->childproc->disconnect(); + d->childproc->clearArguments(); + + d->isRunning = false; + + emit jobExited(d->childproc->normalExit(), d->childproc->exitStatus()); +} + + +void CvsJob::slotReceivedStdout(KProcess* proc, char* buffer, int buflen) +{ + Q_UNUSED(proc); + + QString output = QString::fromLocal8Bit(buffer, buflen); + + // accumulate output + d->outputLines += QStringList::split("\n", output); + + emit receivedStdout(output); +} + + +void CvsJob::slotReceivedStderr(KProcess* proc, char* buffer, int buflen) +{ + Q_UNUSED(proc); + + QString output = QString::fromLocal8Bit(buffer, buflen); + + // accumulate output + d->outputLines += QStringList::split("\n", output); + + emit receivedStderr(output); +} + +#include "cvsjob.moc" diff --git a/cervisia/cvsservice/cvsjob.h b/cervisia/cvsservice/cvsjob.h new file mode 100644 index 00000000..14194499 --- /dev/null +++ b/cervisia/cvsservice/cvsjob.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2002-2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef CVSJOB_H +#define CVSJOB_H + +#include <qobject.h> +#include <qstring.h> +#include <qstringlist.h> +#include <dcopobject.h> + +class KProcess; + + +class KDE_EXPORT CvsJob : public QObject, public DCOPObject +{ + Q_OBJECT + K_DCOP + +public: + explicit CvsJob(unsigned jobNum); + explicit CvsJob(const QString& objId); + virtual ~CvsJob(); + + void clearCvsCommand(); + void setRSH(const QString& rsh); + void setServer(const QString& server); + void setDirectory(const QString& directory); + + CvsJob& operator<<(const QString& arg); + CvsJob& operator<<(const char* arg); + CvsJob& operator<<(const QCString& arg); + CvsJob& operator<<(const QStringList& args); + +k_dcop: + bool execute(); + void cancel(); + + bool isRunning() const; + + /** + * Current cvs command. + * + * @return The current cvs command. Can be null if not set. + */ + QString cvsCommand() const; + + QStringList output() const; + +k_dcop_signals: + void jobExited(bool normalExit, int status); + void receivedStdout(const QString& buffer); + void receivedStderr(const QString& buffer); + +private slots: + void slotProcessExited(); + void slotReceivedStdout(KProcess* proc, char* buffer, int buflen); + void slotReceivedStderr(KProcess* proc, char* buffer, int buflen); + +private: + struct Private; + Private* d; +}; + + +#endif diff --git a/cervisia/cvsservice/cvsloginjob.cpp b/cervisia/cvsservice/cvsloginjob.cpp new file mode 100644 index 00000000..79a9fa08 --- /dev/null +++ b/cervisia/cvsservice/cvsloginjob.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "cvsloginjob.h" + +#include <kdebug.h> +#include <klocale.h> +#include <kpassdlg.h> + +#include <sys/types.h> +#include <signal.h> + +static const char LOGIN_PHRASE[] = "Logging in to"; +static const char FAILURE_PHRASE[] = "authorization failed:"; +static const char PASS_PHRASE[] = "CVS password: "; + + +CvsLoginJob::CvsLoginJob(unsigned jobNum) + : DCOPObject() + , m_Proc(0) +{ + QString objId("CvsLoginJob" + QString::number(jobNum)); + setObjId(objId.local8Bit()); + + m_Proc = new PtyProcess; +} + + +CvsLoginJob::~CvsLoginJob() +{ + delete m_Proc; +} + + +void CvsLoginJob::setServer(const QString& server) +{ + m_Server = server; +} + + +void CvsLoginJob::setCvsClient(const QCString& cvsClient) +{ + m_CvsClient = cvsClient; + + m_Arguments.clear(); + m_Arguments += "-f"; +} + + +void CvsLoginJob::setRepository(const QCString& repository) +{ + m_Arguments += "-d"; + m_Arguments += repository; + m_Arguments += "login"; +} + + +bool CvsLoginJob::execute() +{ + static QCString repository; + + int res = m_Proc->exec(m_CvsClient, m_Arguments); + if( res < 0 ) + { + kdDebug(8051) << "Couldn't start 'cvs login' process!" << endl; + return false; + } + + bool result = false; + while( true ) + { + QCString line = m_Proc->readLine(); + if( line.isNull() ) + { + return result; + } + + // add line to output list + m_output << line; + kdDebug(8051) << "process output = " << line << endl; + + // retrieve repository from 'Logging in to'-line + if( line.contains(LOGIN_PHRASE) ) + { + repository = line.remove(0, line.find(":pserver:")); + continue; + } + + // process asks for the password + // search case insensitive as cvs and cvsnt use different capitalization + if( line.contains(PASS_PHRASE, false) ) + { + kdDebug(8051) << "process waits for the password." << endl; + + // show password dialog + // TODO: We really should display the repository name. Unfortunately + // the dialog doesn't show part of the repository name, because + // it's too long. :-( + QCString password; + int res = KPasswordDialog::getPassword(password, i18n("Please type " + "in your password for the repository below.")); + if( res == KPasswordDialog::Accepted ) + { + // send password to process + m_Proc->WaitSlave(); + m_Proc->writeLine(password); + + // wait for the result + while( !line.contains(FAILURE_PHRASE) ) + { + line = m_Proc->readLine(); + if( line.isNull() ) + return true; + + // add line to output list + m_output << line; + kdDebug(8051) << "process output = " << line << endl; + } + + result = false; + } + else + { + // user pressed cancel so kill the process + kill(m_Proc->pid(), SIGKILL); + m_Proc->waitForChild(); + result = false; + } + } + } + + return false; +} + + +QStringList CvsLoginJob::output() +{ + return m_output; +} diff --git a/cervisia/cvsservice/cvsloginjob.h b/cervisia/cvsservice/cvsloginjob.h new file mode 100644 index 00000000..0754074c --- /dev/null +++ b/cervisia/cvsservice/cvsloginjob.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef CVSLOGINJOB_H +#define CVSLOGINJOB_H + +#include <qstring.h> +#include <qstringlist.h> +#include <dcopobject.h> + +#include <kdesu/process.h> + + +class CvsLoginJob : public DCOPObject +{ + K_DCOP + +public: + explicit CvsLoginJob(unsigned jobNum); + virtual ~CvsLoginJob(); + + void setServer(const QString& server); + + void setCvsClient(const QCString& cvsClient); + void setRepository(const QCString& repository); + +k_dcop: + bool execute(); + QStringList output(); + +private: + PtyProcess* m_Proc; + QString m_Server; + QString m_Rsh; + QCString m_CvsClient; + QCStringList m_Arguments; + QStringList m_output; +}; + + +#endif diff --git a/cervisia/cvsservice/cvsservice.cpp b/cervisia/cvsservice/cvsservice.cpp new file mode 100644 index 00000000..a5f02e20 --- /dev/null +++ b/cervisia/cvsservice/cvsservice.cpp @@ -0,0 +1,1008 @@ +/* + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "cvsservice.h" + +#include <qintdict.h> +#include <qstring.h> + +#include <dcopref.h> +#include <dcopclient.h> +#include <kapplication.h> +#include <kconfig.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kprocess.h> + +#include "cvsjob.h" +#include "cvsloginjob.h" +#include "cvsserviceutils.h" +#include "repository.h" +#include "sshagent.h" + + +static const char SINGLE_JOB_ID[] = "NonConcurrentJob"; +static const char REDIRECT_STDERR[] = "2>&1"; + +enum WatchEvents { None=0, All=1, Commits=2, Edits=4, Unedits=8 }; + +struct CvsService::Private +{ + Private() : singleCvsJob(0), lastJobId(0), repository(0) {} + ~Private() + { + delete repository; + delete singleCvsJob; + } + + CvsJob* singleCvsJob; // non-concurrent cvs job, like update or commit + DCOPRef singleJobRef; // DCOP reference to non-concurrent cvs job + QIntDict<CvsJob> cvsJobs; // concurrent cvs jobs, like diff or annotate + QIntDict<CvsLoginJob> loginJobs; + unsigned lastJobId; + + QCString appId; // cache the DCOP clients app id + + Repository* repository; + + CvsJob* createCvsJob(); + DCOPRef setupNonConcurrentJob(Repository* repo = 0); + + bool hasWorkingCopy(); + bool hasRunningJob(); +}; + + +CvsService::CvsService() + : DCOPObject("CvsService") + , d(new Private) +{ + d->appId = kapp->dcopClient()->appId(); + + // create non-concurrent cvs job + d->singleCvsJob = new CvsJob(SINGLE_JOB_ID); + d->singleJobRef.setRef(d->appId, d->singleCvsJob->objId()); + + // create repository manager + d->repository = new Repository(); + + d->cvsJobs.setAutoDelete(true); + d->loginJobs.setAutoDelete(true); + + KConfig* config = kapp->config(); + KConfigGroupSaver cs(config, "General"); + if( config->readBoolEntry("UseSshAgent", false) ) + { + // use the existing or start a new ssh-agent + SshAgent ssh; + // TODO CL do we need the return value? + //bool res = ssh.querySshAgent(); + ssh.querySshAgent(); + } +} + + +CvsService::~CvsService() +{ + // kill the ssh-agent (when we started it) + SshAgent ssh; + ssh.killSshAgent(); + + d->cvsJobs.clear(); + d->loginJobs.clear(); + delete d; +} + + +DCOPRef CvsService::add(const QStringList& files, bool isBinary) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs add [-kb] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "add"; + + if( isBinary ) + *d->singleCvsJob << "-kb"; + + *d->singleCvsJob << CvsServiceUtils::joinFileList(files) << REDIRECT_STDERR; + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::addWatch(const QStringList& files, int events) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "watch add"; + + if( events != All ) + { + if( events & Commits ) + *d->singleCvsJob << "-a commit"; + if( events & Edits ) + *d->singleCvsJob << "-a edit"; + if( events & Unedits ) + *d->singleCvsJob << "-a unedit"; + } + + *d->singleCvsJob << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::annotate(const QString& fileName, const QString& revision) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // (cvs log [FILE] && cvs annotate [-r rev] [FILE]) + QString quotedName = KProcess::quote(fileName); + QString cvsClient = d->repository->cvsClient(); + + *job << "(" << cvsClient << "log" << quotedName << "&&" + << cvsClient << "annotate"; + + if( !revision.isEmpty() ) + *job << "-r" << revision; + + // *Hack* + // because the string "Annotations for blabla" is + // printed to stderr even with option -Q. + *job << quotedName << ")" << REDIRECT_STDERR; + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::checkout(const QString& workingDir, const QString& repository, + const QString& module, const QString& tag, + bool pruneDirs) +{ + if( d->hasRunningJob() ) + return DCOPRef(); + + Repository repo(repository); + + // assemble the command line + // cd [DIRECTORY] && cvs -d [REPOSITORY] checkout [-r tag] [-P] [MODULE] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "cd" << KProcess::quote(workingDir) << "&&" + << repo.cvsClient() + << "-d" << repository + << "checkout"; + + if( !tag.isEmpty() ) + *d->singleCvsJob << "-r" << tag; + + if( pruneDirs ) + *d->singleCvsJob << "-P"; + + *d->singleCvsJob << module; + + return d->setupNonConcurrentJob(&repo); +} + + +DCOPRef CvsService::checkout(const QString& workingDir, const QString& repository, + const QString& module, const QString& tag, + bool pruneDirs, const QString& alias, bool exportOnly) +{ + if( d->hasRunningJob() ) + return DCOPRef(); + + Repository repo(repository); + + // assemble the command line + // cd [DIRECTORY] && cvs -d [REPOSITORY] co [-r tag] [-P] [-d alias] [MODULE] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "cd" << KProcess::quote(workingDir) << "&&" + << repo.cvsClient() + << "-d" << repository; + if( exportOnly) + *d->singleCvsJob << "export"; + else + *d->singleCvsJob << "checkout"; + + if( !tag.isEmpty() ) + *d->singleCvsJob << "-r" << tag; + + if( pruneDirs && !exportOnly ) + *d->singleCvsJob << "-P"; + + if( !alias.isEmpty() ) + *d->singleCvsJob << "-d" << alias; + + *d->singleCvsJob << module; + + return d->setupNonConcurrentJob(&repo); +} + +DCOPRef CvsService::checkout(const QString& workingDir, const QString& repository, + const QString& module, const QString& tag, + bool pruneDirs, const QString& alias, bool exportOnly, + bool recursive) +{ + if( d->hasRunningJob() ) + return DCOPRef(); + + Repository repo(repository); + + // assemble the command line + // cd [DIRECTORY] && cvs -d [REPOSITORY] co [-r tag] [-P] [-d alias] [MODULE] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "cd" << KProcess::quote(workingDir) << "&&" + << repo.cvsClient() + << "-d" << repository; + if( exportOnly) + *d->singleCvsJob << "export"; + else + *d->singleCvsJob << "checkout"; + + if( !tag.isEmpty() ) + *d->singleCvsJob << "-r" << tag; + + if( pruneDirs && !exportOnly ) + *d->singleCvsJob << "-P"; + + if( !alias.isEmpty() ) + *d->singleCvsJob << "-d" << alias; + + if( ! recursive ) + *d->singleCvsJob << "-l"; + + *d->singleCvsJob << module; + + return d->setupNonConcurrentJob(&repo); +} + +DCOPRef CvsService::commit(const QStringList& files, const QString& commitMessage, + bool recursive) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs commit [-l] [-m MESSAGE] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "commit"; + + if( !recursive ) + *d->singleCvsJob << "-l"; + + *d->singleCvsJob << "-m" << KProcess::quote(commitMessage) + << CvsServiceUtils::joinFileList(files) << REDIRECT_STDERR; + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::createRepository(const QString& repository) +{ + if( d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs -d [REPOSITORY] init + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "mkdir -p" << KProcess::quote(repository) << "&&" + << d->repository->cvsClient() + << "-d" << KProcess::quote(repository) + << "init"; + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::createTag(const QStringList& files, const QString& tag, + bool branch, bool force) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs tag [-b] [-F] [TAG] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "tag"; + + if( branch ) + *d->singleCvsJob << "-b"; + + if( force ) + *d->singleCvsJob << "-F"; + + *d->singleCvsJob << KProcess::quote(tag) + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::deleteTag(const QStringList& files, const QString& tag, + bool branch, bool force) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs tag -d [-b] [-F] [TAG] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "tag" << "-d"; + + if( branch ) + *d->singleCvsJob << "-b"; + + if( force ) + *d->singleCvsJob << "-F"; + + *d->singleCvsJob << KProcess::quote(tag) + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::downloadCvsIgnoreFile(const QString& repository, + const QString& outputFile) +{ + Repository repo(repository); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs -d [REPOSITORY] -q checkout -p CVSROOT/cvsignore > [OUTPUTFILE] + *job << repo.cvsClient() << "-d" << repository + << "-q checkout -p CVSROOT/cvsignore >" + << KProcess::quote(outputFile); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::downloadRevision(const QString& fileName, + const QString& revision, + const QString& outputFile) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs update -p -r [REV] [FILE] > [OUTPUTFILE] + *job << d->repository->cvsClient() << "update -p"; + + if( !revision.isEmpty() ) + *job << "-r" << KProcess::quote(revision); + + *job << KProcess::quote(fileName) << ">" << KProcess::quote(outputFile); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::downloadRevision(const QString& fileName, + const QString& revA, + const QString& outputFileA, + const QString& revB, + const QString& outputFileB) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs update -p -r [REVA] [FILE] > [OUTPUTFILEA] ; + // cvs update -p -r [REVB] [FILE] > [OUTPUTFILEB] + *job << d->repository->cvsClient() << "update -p" + << "-r" << KProcess::quote(revA) + << KProcess::quote(fileName) << ">" << KProcess::quote(outputFileA) + << ";" << d->repository->cvsClient() << "update -p" + << "-r" << KProcess::quote(revB) + << KProcess::quote(fileName) << ">" << KProcess::quote(outputFileB); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::diff(const QString& fileName, const QString& revA, + const QString& revB, const QString& diffOptions, + unsigned contextLines) +{ + // cvs diff [DIFFOPTIONS] -U CONTEXTLINES [-r REVA] {-r REVB] [FILE] + QString format = "-U" + QString::number(contextLines); + return diff(fileName, revA, revB, diffOptions, format); +} + + +DCOPRef CvsService::diff(const QString& fileName, const QString& revA, + const QString& revB, const QString& diffOptions, + const QString& format) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs diff [DIFFOPTIONS] [FORMAT] [-r REVA] {-r REVB] [FILE] + *job << d->repository->cvsClient() << "diff" << diffOptions + << format; + + if( !revA.isEmpty() ) + *job << "-r" << KProcess::quote(revA); + + if( !revB.isEmpty() ) + *job << "-r" << KProcess::quote(revB); + + *job << KProcess::quote(fileName); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::edit(const QStringList& files) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs edit [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "edit" + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::editors(const QStringList& files) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs editors [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "editors" + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::history() +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs history -e -a + *job << d->repository->cvsClient() << "history -e -a"; + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::import(const QString& workingDir, const QString& repository, + const QString& module, const QString& ignoreList, + const QString& comment, const QString& vendorTag, + const QString& releaseTag, bool importAsBinary) +{ + if( d->hasRunningJob() ) + return DCOPRef(); + + Repository repo(repository); + + // assemble the command line + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "cd" << KProcess::quote(workingDir) << "&&" + << repo.cvsClient() + << "-d" << repository + << "import"; + + if( importAsBinary ) + *d->singleCvsJob << "-kb"; + + const QString ignore = ignoreList.stripWhiteSpace(); + if( !ignore.isEmpty() ) + *d->singleCvsJob << "-I" << KProcess::quote(ignore); + + QString logMessage = comment.stripWhiteSpace(); + logMessage.prepend("\""); + logMessage.append("\""); + *d->singleCvsJob << "-m" << logMessage; + + *d->singleCvsJob << module << vendorTag << releaseTag; + + return d->setupNonConcurrentJob(&repo); +} + + +DCOPRef CvsService::import(const QString& workingDir, const QString& repository, + const QString& module, const QString& ignoreList, + const QString& comment, const QString& vendorTag, + const QString& releaseTag, bool importAsBinary, + bool useModificationTime) +{ + if( d->hasRunningJob() ) + return DCOPRef(); + + Repository repo(repository); + + // assemble the command line + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "cd" << KProcess::quote(workingDir) << "&&" + << repo.cvsClient() + << "-d" << repository + << "import"; + + if( importAsBinary ) + *d->singleCvsJob << "-kb"; + + if( useModificationTime ) + *d->singleCvsJob << "-d"; + + const QString ignore = ignoreList.stripWhiteSpace(); + if( !ignore.isEmpty() ) + *d->singleCvsJob << "-I" << KProcess::quote(ignore); + + QString logMessage = comment.stripWhiteSpace(); + logMessage.prepend("\""); + logMessage.append("\""); + *d->singleCvsJob << "-m" << logMessage; + + *d->singleCvsJob << module << vendorTag << releaseTag; + + return d->setupNonConcurrentJob(&repo); +} + + +DCOPRef CvsService::lock(const QStringList& files) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs admin -l [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "admin -l" + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::log(const QString& fileName) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs log [FILE] + *job << d->repository->cvsClient() << "log" << KProcess::quote(fileName); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::login(const QString& repository) +{ + if( repository.isEmpty() ) + return DCOPRef(); + + Repository repo(repository); + + // create a cvs job + ++(d->lastJobId); + + CvsLoginJob* job = new CvsLoginJob(d->lastJobId); + d->loginJobs.insert(d->lastJobId, job); + + // TODO: CVS_SERVER doesn't work ATM +// job->setServer(repo.server()); + + // assemble the command line + // cvs -d [REPOSITORY] login + job->setCvsClient(repo.clientOnly().local8Bit()); + job->setRepository(repository.local8Bit()); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::logout(const QString& repository) +{ + if( repository.isEmpty() ) + return DCOPRef(); + + Repository repo(repository); + + // create a cvs job + ++(d->lastJobId); + + CvsJob* job = new CvsJob(d->lastJobId); + d->cvsJobs.insert(d->lastJobId, job); + + job->setRSH(repo.rsh()); + job->setServer(repo.server()); + job->setDirectory(repo.workingCopy()); + + // assemble the command line + // cvs -d [REPOSITORY] logout + *job << repo.cvsClient() << "-d" << repository << "logout"; + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::makePatch() +{ + return makePatch("", "-u"); +} + + +DCOPRef CvsService::makePatch(const QString& diffOptions, const QString& format) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs diff [DIFFOPTIONS] [FORMAT] -R 2>/dev/null + *job << d->repository->cvsClient() << "diff" << diffOptions << format << "-R" + << "2>/dev/null"; + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::moduleList(const QString& repository) +{ + Repository repo(repository); + + // create a cvs job + ++(d->lastJobId); + + CvsJob* job = new CvsJob(d->lastJobId); + d->cvsJobs.insert(d->lastJobId, job); + + job->setRSH(repo.rsh()); + job->setServer(repo.server()); + job->setDirectory(repo.workingCopy()); + + // assemble the command line + // cvs -d [REPOSITORY] checkout -c + *job << repo.cvsClient() << "-d" << repository << "checkout -c"; + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::remove(const QStringList& files, bool recursive) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs remove -f [-l] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "remove -f"; + + if( !recursive ) + *d->singleCvsJob << "-l"; + + *d->singleCvsJob << CvsServiceUtils::joinFileList(files) << REDIRECT_STDERR; + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::removeWatch(const QStringList& files, int events) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "watch remove"; + + if( events != All ) + { + if( events & Commits ) + *d->singleCvsJob << "-a commit"; + if( events & Edits ) + *d->singleCvsJob << "-a edit"; + if( events & Unedits ) + *d->singleCvsJob << "-a unedit"; + } + + *d->singleCvsJob << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::rlog(const QString& repository, const QString& module, + bool recursive) +{ + Repository repo(repository); + + // create a cvs job + ++(d->lastJobId); + + CvsJob* job = new CvsJob(d->lastJobId); + d->cvsJobs.insert(d->lastJobId, job); + + job->setRSH(repo.rsh()); + job->setServer(repo.server()); + + // assemble the command line + // cvs -d [REPOSITORY] rlog [-l] [MODULE] + *job << repo.cvsClient() << "-d" << repository << "rlog"; + + if( !recursive ) + *job << "-l"; + + *job << module; + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::simulateUpdate(const QStringList& files, bool recursive, + bool createDirs, bool pruneDirs) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs -n update [-l] [-d] [-P] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "-n -q update"; + + if( !recursive ) + *d->singleCvsJob << "-l"; + + if( createDirs ) + *d->singleCvsJob << "-d"; + + if( pruneDirs ) + *d->singleCvsJob << "-P"; + + *d->singleCvsJob << CvsServiceUtils::joinFileList(files) << REDIRECT_STDERR; + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::status(const QStringList& files, bool recursive, bool tagInfo) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs status [-l] [-v] [FILES] + *job << d->repository->cvsClient() << "status"; + + if( !recursive ) + *job << "-l"; + + if( tagInfo ) + *job << "-v"; + + *job << CvsServiceUtils::joinFileList(files); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::unedit(const QStringList& files) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // echo y | cvs unedit [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "echo y |" + << d->repository->cvsClient() << "unedit" + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::unlock(const QStringList& files) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs admin -u [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "admin -u" + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::update(const QStringList& files, bool recursive, + bool createDirs, bool pruneDirs, const QString& extraOpt) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs update [-l] [-d] [-P] [EXTRAOPTIONS] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "-q update"; + + if( !recursive ) + *d->singleCvsJob << "-l"; + + if( createDirs ) + *d->singleCvsJob << "-d"; + + if( pruneDirs ) + *d->singleCvsJob << "-P"; + + *d->singleCvsJob << extraOpt << CvsServiceUtils::joinFileList(files) + << REDIRECT_STDERR; + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::watchers(const QStringList& files) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs watchers [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "watchers" + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +void CvsService::quit() +{ + kapp->quit(); +} + + +CvsJob* CvsService::Private::createCvsJob() +{ + ++lastJobId; + + // create a cvs job + CvsJob* job = new CvsJob(lastJobId); + cvsJobs.insert(lastJobId, job); + + job->setRSH(repository->rsh()); + job->setServer(repository->server()); + job->setDirectory(repository->workingCopy()); + + return job; +} + + +DCOPRef CvsService::Private::setupNonConcurrentJob(Repository* repo) +{ + // no explicit repository provided? + if( !repo ) + repo = repository; + + singleCvsJob->setRSH(repo->rsh()); + singleCvsJob->setServer(repo->server()); + singleCvsJob->setDirectory(repo->workingCopy()); + + return singleJobRef; +} + + +bool CvsService::Private::hasWorkingCopy() +{ + if( repository->workingCopy().isEmpty() ) + { + KMessageBox::sorry(0, i18n("You have to set a local working copy " + "directory before you can use this function!")); + return false; + } + + return true; +} + + +bool CvsService::Private::hasRunningJob() +{ + bool result = singleCvsJob->isRunning(); + + if( result ) + KMessageBox::sorry(0, i18n("There is already a job running")); + + return result; +} diff --git a/cervisia/cvsservice/cvsservice.desktop b/cervisia/cvsservice/cvsservice.desktop new file mode 100644 index 00000000..31cd6dad --- /dev/null +++ b/cervisia/cvsservice/cvsservice.desktop @@ -0,0 +1,73 @@ +[Desktop Entry] +Type=Service +Name=CvsService +Name[br]=Servij Cvs +Name[bs]=CvsServis +Name[ca]=Servei de CVS +Name[cs]=CVS služba +Name[cy]=GwasanaethCVS +Name[de]=CVS-Dienst +Name[el]=Cvs υπηρεσία +Name[eo]=CvsServo +Name[es]=Servicio CVS +Name[et]=CVS teenus +Name[fi]=Cvs-palvelu +Name[gl]=Servizo CVS +Name[hi]=सीवीएस-सर्विस +Name[hu]=CVS szolgáltatás +Name[it]=Servizio CVS +Name[ja]=CVS サービス +Name[lt]=CvsTarnyba +Name[nds]=CVS-Deenst +Name[pt_BR]=ServiçoCVS +Name[sv]=CVS-tjänst +Name[ta]=Cvsசேவை +Name[tg]=ҲизматиCvs +Name[zh_TW]=CVS 服務 +Exec=cvsservice +X-DCOP-ServiceType=Multi +X-KDE-StartupNotify=false +Comment=A DCOP service that provides an interface to cvs +Comment[bg]=Услуга на DCOP, която предлага интерфейс към CVS +Comment[bs]=DCOP servis koji pruža interfejs na CVS +Comment[ca]=Un servei DCOP que proporciona una interfície per al cvs +Comment[cs]=DCOP služba poskytující rozhraní k CVS +Comment[cy]=Gwasanaeth DCOP sy'n darparu rhyngwyneb i cvs +Comment[da]=En DCOP-tjeneste der sørger for en CVS-grænseflade +Comment[de]=Ein DCOP-Dienst, der eine Schnittstelle zu CVS bereitstellt +Comment[el]=Μια υπηρεσία DCOP που προσφέρει ένα περιβάλλον χρήσης για το cvs +Comment[es]=Un servicio DCOP que proporciona una interfaz para cvs +Comment[et]=CVSi DCOP liidese teenus +Comment[eu]=cvs-rako interfazea eskeintzen duen DCOP zerbitzua +Comment[fa]=خدمت DCOP که واسطی را برای cvs فراهم میکند +Comment[fi]=DCOP-palvelu, joka tarjoaa rajapinnan cvs:lle +Comment[fr]=Un service DCOP qui fournit une interface à CVS +Comment[gl]=Un servizo de DCOP que fornece unha interface para CVS +Comment[hi]=एक डीकॉप सर्विस जो सीवीएस को इंटरफेस प्रदान करता है +Comment[hu]=DCOP-alapú szolgáltatás a CVS eléréséhez +Comment[is]=DCOP þjónusta sem veitir viðmót á cvs +Comment[it]=Un servizio DCOP che fornisce un'interfaccia a cvs +Comment[ja]=CVS インターフェースを提供する DCOP サービス +Comment[ka]=DCOP სერვისი, რომელიც cvs-ს ინტერფეისს შეიცავს +Comment[kk]=CVS интерфейсін қамтамасыз ететін DCOP қызметі +Comment[lt]=DCOP tarnyba, pateikianti cvs sąsają +Comment[nb]=En DCOP-tjeneste som tilbyr et grensesnitt mot cvs +Comment[nds]=En DCOP-Deenst, wat en Koppelsteed na CVS praatstellt +Comment[ne]=cvs मा इन्टरफेस उपलब्ध गर्ने एउटा डीसीओपी कार्य +Comment[nl]=Een DCOP-dienst die een interface naar cvs biedt +Comment[nn]=Ei DCOP-teneste som tilbyr eit grensesnitt mot CVS +Comment[pl]=Usługa DCOP pozwalająca na dostęp do CVS +Comment[pt]=Um serviço de DCOP que oferece uma interface para o CVS +Comment[pt_BR]=Um serviço DCOP que provê uma interface para o cvs +Comment[ru]=Сервис DCOP для интерфейса с cvs +Comment[sk]=Služba DCOP pre prístup k CVS +Comment[sl]=Storitev DCOP, ki omogoča vmesnik do CVS +Comment[sr]=DCOP сервис који пружа интерфејс за CVS +Comment[sr@Latn]=DCOP servis koji pruža interfejs za CVS +Comment[sv]=DCOP-tjänst som tillhandahåller ett gränssnitt till CVS +Comment[ta]=ஒரு cvsக்கு ஒரு இடைமுகத்தினை அளிக்கக்கூடிய DCOPசேவை +Comment[tg]=Хизмати DCOP барои интерфейс бо cvs +Comment[tr]= CVS'ye arayüz sağlayan bir DCOP Servisi +Comment[uk]=Служба DCOP, яка надає інтерфейс до cvs +Comment[zh_CN]=提供 CVS 接口的 DCOP 服务 +Comment[zh_TW]=提供 cvs 介面的 DCOP 服務 diff --git a/cervisia/cvsservice/cvsservice.h b/cervisia/cvsservice/cvsservice.h new file mode 100644 index 00000000..0cc43e66 --- /dev/null +++ b/cervisia/cvsservice/cvsservice.h @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef CVSSERVICE_H +#define CVSSERVICE_H + +#include <qstringlist.h> +#include <dcopref.h> +#include <dcopobject.h> + +class QString; + + +class KDE_EXPORT CvsService : public DCOPObject +{ + K_DCOP + +public: + CvsService(); + ~CvsService(); + +k_dcop: + /** + * Adds new files to an existing project. The files don't actually + * appear in the repository until a subsequent commit is performed. + * + * @param files A list of files that should be added to the repository. + * @param isBinary Set to true to treat the files as binary files (-kb) + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef add(const QStringList& files, bool isBinary); + + /** + */ + DCOPRef addWatch(const QStringList& files, int events); + + /** + * Shows information on who last modified each line of a file and when. + * + * @param fileName the name of the file to show annotations for + * @param revision show annotations for this revision (number or tag) + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef annotate(const QString& fileName, const QString& revision); + + /** + * Checks out a module from the repository into a working copy. + * + * @param workingDir path to a local working copy directory + * @param repository + * @param module the name of the module + * @param tag + * @param pruneDirs remove empty directories from the working copy. + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef checkout(const QString& workingDir, const QString& repository, + const QString& module, const QString& tag, bool pruneDirs); + + /** + * Checks out a module from the repository into a working copy. + * + * @param workingDir path to a local working copy directory + * @param repository + * @param module the name of the module + * @param tag + * @param pruneDirs remove empty directories from the working copy. + * @param alias alternative directory to check out to + * @param exportOnly flag to show we want a cvs export rather than a checkout + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + //### KDE4: merge with above checkout() method + DCOPRef checkout(const QString& workingDir, const QString& repository, + const QString& module, const QString& tag, bool pruneDirs, + const QString& alias, bool exportOnly); + + /** + * Checks out a module from the repository into a working copy. + * + * @param workingDir path to a local working copy directory + * @param repository + * @param module the name of the module + * @param tag + * @param pruneDirs remove empty directories from the working copy. + * @param alias alternative directory to check out to + * @param exportOnly flag to show we want a cvs export rather than a checkout + * @param recursive check out dirs recursively + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef checkout(const QString& workingDir, const QString& repository, + const QString& module, const QString& tag, bool pruneDirs, + const QString& alias, bool exportOnly, bool recursive); + + /** + * + * @param files A list of files with changes that should be committed to + * the repository. + * @param commitMessage log message describing the changes + * @param recursive + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef commit(const QStringList& files, const QString& commitMessage, + bool recursive); + + /** + * Creates a new root repository. + * + * @param repository + */ + DCOPRef createRepository(const QString& repository); + + /** + */ + DCOPRef createTag(const QStringList& files, const QString& tag, + bool branch, bool force); + + /** + */ + DCOPRef deleteTag(const QStringList& files, const QString& tag, + bool branch, bool force); + + /** + */ + DCOPRef downloadCvsIgnoreFile(const QString& repository, + const QString& outputFile); + + /** + */ + DCOPRef downloadRevision(const QString& fileName, const QString& revision, + const QString& outputFile); + + /** + */ + DCOPRef downloadRevision(const QString& fileName, const QString& revA, + const QString& outputFileA, const QString& revB, + const QString& outputFileB); + + /** + * + * @param fileName + * @param revA + * @param revB + * @param diffOptions + * @param contextLines + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef diff(const QString& fileName, const QString& revA, + const QString& revB, const QString& diffOptions, + unsigned contextLines); + + /** + * + * @param fileName + * @param revA + * @param revB + * @param diffOptions + * @param format + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef diff(const QString& fileName, const QString& revA, + const QString& revB, const QString& diffOptions, + const QString& format); + + /** + * @param files + */ + DCOPRef edit(const QStringList& files); + + /** + * @param files + */ + DCOPRef editors(const QStringList& files); + + /** + * Shows a history of activity (like checkouts, commits, etc) in the + * repository for all users and all record types. + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef history(); + + /** + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef import(const QString& workingDir, const QString& repository, + const QString& module, const QString& ignoreList, + const QString& comment, const QString& vendorTag, + const QString& releaseTag, bool importAsBinary); + + /** + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + //### KDE4: merge with above import() method + DCOPRef import(const QString& workingDir, const QString& repository, + const QString& module, const QString& ignoreList, + const QString& comment, const QString& vendorTag, + const QString& releaseTag, bool importAsBinary, + bool useModificationTime); + + /** + * @param files + */ + DCOPRef lock(const QStringList& files); + + /** + * Shows log messages for a file. + * + * @param fileName the name of the file to show log messages for + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef log(const QString& fileName); + + /** + * @param repository + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef login(const QString& repository); + + /** + * @param repository + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef logout(const QString& repository); + + /** + */ + DCOPRef makePatch(); + + /** + */ + //### KDE4: merge with above makePatch() method + DCOPRef makePatch(const QString& diffOptions, const QString& format); + + /** + * @param repository + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef moduleList(const QString& repository); + + /** + * Deletes files from the local working copy and schedules them to be + * removed from the repository. The files don't actually disappear from + * the repository until a subsequent commit is performed. + * + * @param files A list of files that should be removed from the repository. + * @param recursive descend into subdirectories. + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef remove(const QStringList& files, bool recursive); + + /** + */ + DCOPRef removeWatch(const QStringList& files, int events); + + /** + */ + DCOPRef rlog(const QString& repository, const QString& module, + bool recursive); + + /** + * Shows a summary of what's been done locally, without changing the + * working copy. (cvs -n update) + * + * @param files + * @param recursive descend into subdirectories. + * @param createDirs + * @param pruneDirs + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef simulateUpdate(const QStringList& files, bool recursive, + bool createDirs, bool pruneDirs); + + /** + * Shows the status of the files in the working copy. + * + * @param files + * @param recursive descend into subdirectories. + * @param tagInfo show tag information for the file. + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef status(const QStringList& files, bool recursive, bool tagInfo); + + /** + * @param files + */ + DCOPRef unedit(const QStringList& files); + + /** + * @param files + */ + DCOPRef unlock(const QStringList& files); + + /** + * Merges changes from the repository into the files of the + * working copy. + * + * @param files A list of files that should be updated. + * @param recursive descend into subdirectories. + * @param createDirs create directories that exist in the repository + * but not yet in the working copy. + * @param pruneDirs remove empty directories from the working copy. + * @param extraOpt + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef update(const QStringList& files, bool recursive, bool createDirs, + bool pruneDirs, const QString& extraOpt); + + /** + * @param files + */ + DCOPRef watchers(const QStringList& files); + + /** + * Quits the DCOP service. + */ + void quit(); + +private: + struct Private; + Private* d; +}; + + +#endif diff --git a/cervisia/cvsservice/cvsserviceutils.cpp b/cervisia/cvsservice/cvsserviceutils.cpp new file mode 100644 index 00000000..73bfc531 --- /dev/null +++ b/cervisia/cvsservice/cvsserviceutils.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "cvsserviceutils.h" + +#include <qstring.h> +#include <qstringlist.h> +#include <kprocess.h> + + +QString CvsServiceUtils::joinFileList(const QStringList& files) +{ + QString result; + + QStringList::ConstIterator it = files.begin(); + QStringList::ConstIterator end = files.end(); + + for( ; it != end; ++it ) + { + result += KProcess::quote(*it); + result += " "; + } + + if( result.length() > 0 ) + result.truncate(result.length()-1); + + return result; +} diff --git a/cervisia/cvsservice/cvsserviceutils.h b/cervisia/cvsservice/cvsserviceutils.h new file mode 100644 index 00000000..8fb7290f --- /dev/null +++ b/cervisia/cvsservice/cvsserviceutils.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef CVSSERVICE_UTILS_H +#define CVSSERVICE_UTILS_H + +class QString; +class QStringList; + + +namespace CvsServiceUtils +{ + +/** + * Joins a list of file names to one QString and quotes + * each name properly for usage with KProcess. + */ +QString joinFileList(const QStringList& files); + +} + + +#endif diff --git a/cervisia/cvsservice/main.cpp b/cervisia/cvsservice/main.cpp new file mode 100644 index 00000000..4f6c748d --- /dev/null +++ b/cervisia/cvsservice/main.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2002 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <klocale.h> +#include "cvsservice.h" + + +extern "C" KDE_EXPORT int kdemain(int argc, char** argv) +{ + KAboutData about("cvsservice", I18N_NOOP("CVS DCOP service"), "0.1", + I18N_NOOP("DCOP service for CVS"), KAboutData::License_LGPL, + "Copyright (c) 2002-2003 Christian Loose"); + about.addAuthor("Christian Loose", I18N_NOOP("Developer"), + "christian.loose@hamburg.de"); + + KCmdLineArgs::init(argc, argv, &about); + + KApplication app; + + // This app is started automatically, no need for session management + app.disableSessionManagement(); + + CvsService service; + + return app.exec(); +} diff --git a/cervisia/cvsservice/repository.cpp b/cervisia/cvsservice/repository.cpp new file mode 100644 index 00000000..d2cff113 --- /dev/null +++ b/cervisia/cvsservice/repository.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "repository.h" + +#include <qdir.h> +#include <qfile.h> +#include <qstring.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <kdirwatch.h> +#include <kstandarddirs.h> + +#include "sshagent.h" + + +struct Repository::Private +{ + Private() : compressionLevel(0) {} + + QString configFileName; + + QString workingCopy; + QString location; + + QString client; + QString rsh; + QString server; + int compressionLevel; + bool retrieveCvsignoreFile; + + void readConfig(); + void readGeneralConfig(); +}; + + + +Repository::Repository() + : QObject() + , DCOPObject("CvsRepository") + , d(new Private) +{ + d->readGeneralConfig(); + + // other cvsservice instances might change the configuration file + // so we watch it for changes + d->configFileName = locate("config", "cvsservicerc"); + KDirWatch* fileWatcher = new KDirWatch(this); + connect(fileWatcher, SIGNAL(dirty(const QString&)), + this, SLOT(slotConfigDirty(const QString&))); + fileWatcher->addFile(d->configFileName); +} + + +Repository::Repository(const QString& repository) + : QObject() + , DCOPObject() + , d(new Private) +{ + d->location = repository; + d->readGeneralConfig(); + d->readConfig(); + + // other cvsservice instances might change the configuration file + // so we watch it for changes + d->configFileName = locate("config", "cvsservicerc"); + KDirWatch* fileWatcher = new KDirWatch(this); + connect(fileWatcher, SIGNAL(dirty(const QString&)), + this, SLOT(slotConfigDirty(const QString&))); + fileWatcher->addFile(d->configFileName); +} + + +Repository::~Repository() +{ + delete d; +} + + +QString Repository::cvsClient() const +{ + QString client(d->client); + + // suppress reading of the '.cvsrc' file + client += " -f"; + + // we don't need the command line option if there is no compression level set + if( d->compressionLevel > 0 ) + { + client += " -z" + QString::number(d->compressionLevel) + " "; + } + + return client; +} + + +QString Repository::clientOnly() const +{ + return d->client; +} + + +QString Repository::rsh() const +{ + return d->rsh; +} + + +QString Repository::server() const +{ + return d->server; +} + + +bool Repository::setWorkingCopy(const QString& dirName) +{ + const QFileInfo fi(dirName); + const QString path = fi.absFilePath(); + + // is this really a cvs-controlled directory? + const QFileInfo cvsDirInfo(path + "/CVS"); + if( !cvsDirInfo.exists() || !cvsDirInfo.isDir() || + !QFile::exists( cvsDirInfo.filePath() + "/Entries" ) || + !QFile::exists( cvsDirInfo.filePath() + "/Repository" ) || + !QFile::exists( cvsDirInfo.filePath() + "/Root" ) ) + return false; + + d->workingCopy = path; + d->location = QString::null; + + // determine path to the repository + QFile rootFile(path + "/CVS/Root"); + if( rootFile.open(IO_ReadOnly) ) + { + QTextStream stream(&rootFile); + d->location = stream.readLine(); + } + rootFile.close(); + + // add identities (ssh-add) to ssh-agent + // TODO CL make sure this is called only once + if( d->location.contains(":ext:", false) > 0 ) + { + SshAgent ssh; + ssh.addSshIdentities(); + } + + QDir::setCurrent(path); + d->readConfig(); + + return true; +} + + +QString Repository::workingCopy() const +{ + return d->workingCopy; +} + + +QString Repository::location() const +{ + return d->location; +} + + +bool Repository::retrieveCvsignoreFile() const +{ + return d->retrieveCvsignoreFile; +} + + +void Repository::slotConfigDirty(const QString& fileName) +{ + if( fileName == d->configFileName ) + { + // reread the configuration data from disk + kapp->config()->reparseConfiguration(); + d->readConfig(); + } +} + + +void Repository::Private::readGeneralConfig() +{ + KConfig* config = kapp->config(); + + // get path to cvs client programm + config->setGroup("General"); + client = config->readPathEntry("CVSPath", "cvs"); +} + + +void Repository::Private::readConfig() +{ + KConfig* config = kapp->config(); + + // Sometimes the location can be unequal to the entry in the CVS/Root. + // + // This can happen when the checkout was done with a repository name + // like :pserver:user@cvs.kde.org:/home/kde. When cvs then saves the + // name into the .cvspass file, it adds the default cvs port to it like + // this :pserver:user@cvs.kde.org:2401/home/kde. This name is then also + // used for the configuration group. + // + // In order to be able to read this group, we then have to manually add + // the port number to it. + QString repositoryGroup = QString::fromLatin1("Repository-") + location; + if( !config->hasGroup(repositoryGroup) ) + { + // find the position of the first path separator + const int insertPos = repositoryGroup.find('/'); + if( insertPos > 0 ) + { + // add port to location + // (1) :pserver:user@hostname.com:/path + if( repositoryGroup.at(insertPos - 1) == ':' ) + repositoryGroup.insert(insertPos, "2401"); + // (2) :pserver:user@hostname.com/path + else + repositoryGroup.insert(insertPos, ":2401"); + } + } + + config->setGroup(repositoryGroup); + + // should we retrieve the CVSROOT/cvsignore file from the cvs server? + retrieveCvsignoreFile = config->readBoolEntry("RetrieveCvsignore", false); + + // see if there is a specific compression level set for this repository + compressionLevel = config->readNumEntry("Compression", -1); + + // use default global compression level instead? + if( compressionLevel < 0 ) + { + KConfigGroupSaver cs(config, "General"); + compressionLevel = config->readNumEntry("Compression", 0); + } + + // get remote shell client to access the remote repository + rsh = config->readPathEntry("rsh"); + + // get program to start on the server side + server = config->readEntry("cvs_server"); +} + + +#include "repository.moc" diff --git a/cervisia/cvsservice/repository.h b/cervisia/cvsservice/repository.h new file mode 100644 index 00000000..c90277bc --- /dev/null +++ b/cervisia/cvsservice/repository.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2002-2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef REPOSITORY_H +#define REPOSITORY_H + +#include <qobject.h> +#include <dcopobject.h> + +class QString; + + +/** + * Represents a local or remote cvs repository with + * its repository-specific configuration data. + */ +class KDE_EXPORT Repository : public QObject, public DCOPObject +{ + K_DCOP + Q_OBJECT + +public: + Repository(); + explicit Repository(const QString& repository); + ~Repository(); + + /** + * cvs command (including the user-specified path) with the options + * for this repository. + * + * @return A cvs command (including path). + */ + QString cvsClient() const; + + /** + */ + QString clientOnly() const; + + /** + * Remote shell command line client which should be used to + * access the remote cvs repository, when :ext: access method + * is specified. ($CVS_RSH) + * + * @return The remote shell client. Can be null if not set. + */ + QString rsh() const; + + /** + * Program to start on the server side when accessing a remote + * repository using :ext: access method. ($CVS_SERVER) + * + * @return The server program. Can be null if not set. + */ + QString server() const; + +k_dcop: + /** + * Changes the working copy and the corresponding cvs repository. + * + * @param dirName path to the local working copy directory. + */ + bool setWorkingCopy(const QString& dirName); + + /** + * Path to the current working copy. + * + * @return The working copy directory. Can be null if not set. + */ + QString workingCopy() const; + + /** + * Path and method to access the current cvs repository. + * i.e. :pserver:user@cvs.project.org:/home/project + * + * @return The path and method to access the cvs repository. + */ + QString location() const; + + /** + */ + bool retrieveCvsignoreFile() const; + +private slots: + void slotConfigDirty(const QString& fileName); + +private: + struct Private; + Private* d; +}; + + +#endif diff --git a/cervisia/cvsservice/sshagent.cpp b/cervisia/cvsservice/sshagent.cpp new file mode 100644 index 00000000..48bc5eef --- /dev/null +++ b/cervisia/cvsservice/sshagent.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "sshagent.h" + +#include <qregexp.h> +#include <kapplication.h> +#include <kdebug.h> +#include <kdeversion.h> +#include <kprocess.h> + +#include <stdlib.h> + + +// initialize static member variables +bool SshAgent::m_isRunning = false; +bool SshAgent::m_isOurAgent = false; +QString SshAgent::m_authSock = QString::null; +QString SshAgent::m_pid = QString::null; + + +SshAgent::SshAgent(QObject* parent, const char* name) + : QObject(parent, name) +{ +} + + +SshAgent::~SshAgent() +{ +} + + +bool SshAgent::querySshAgent() +{ + kdDebug(8051) << "SshAgent::querySshAgent(): ENTER" << endl; + + if( m_isRunning ) + return true; + + // Did the user already start a ssh-agent process? + char* pid; + if( (pid = ::getenv("SSH_AGENT_PID")) != 0 ) + { + kdDebug(8051) << "SshAgent::querySshAgent(): ssh-agent already exists" + << endl; + + m_pid = QString::fromLocal8Bit(pid); + + char* sock = ::getenv("SSH_AUTH_SOCK"); + if( sock ) + m_authSock = QString::fromLocal8Bit(sock); + + m_isOurAgent = false; + m_isRunning = true; + } + // We have to start a new ssh-agent process + else + { + kdDebug(8051) << "SshAgent::querySshAgent(): start ssh-agent" << endl; + + m_isOurAgent = true; + m_isRunning = startSshAgent(); + } + + return m_isRunning; +} + + +bool SshAgent::addSshIdentities() +{ + kdDebug(8051) << "SshAgent::addSshIdentities(): ENTER" << endl; + + if( !m_isRunning || !m_isOurAgent ) + return false; + + // add identities to ssh-agent + KProcess proc; + + proc.setEnvironment("SSH_AGENT_PID", m_pid); + proc.setEnvironment("SSH_AUTH_SOCK", m_authSock); + proc.setEnvironment("SSH_ASKPASS", "cvsaskpass"); + + proc << "ssh-add"; + + connect(&proc, SIGNAL(receivedStdout(KProcess*, char*, int)), + SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(&proc, SIGNAL(receivedStderr(KProcess*, char*, int)), + SLOT(slotReceivedStderr(KProcess*, char*, int))); + + proc.start(KProcess::DontCare, KProcess::AllOutput); + + // wait for process to finish + // TODO CL use timeout? + proc.wait(); + + kdDebug(8051) << "SshAgent::slotProcessExited(): added identities" << endl; + + return (proc.normalExit() && proc.exitStatus() == 0); +} + + +void SshAgent::killSshAgent() +{ + kdDebug(8051) << "SshAgent::killSshAgent(): ENTER" << endl; + + if( !m_isRunning || !m_isOurAgent ) + return; + + KProcess proc; + + proc << "kill" << m_pid; + + proc.start(KProcess::DontCare, KProcess::NoCommunication); + + kdDebug(8051) << "SshAgent::killSshAgent(): killed pid = " << m_pid << endl; +} + + +void SshAgent::slotProcessExited(KProcess*) +{ + kdDebug(8051) << "SshAgent::slotProcessExited(): ENTER" << endl; + + QRegExp cshPidRx("setenv SSH_AGENT_PID (\\d*);"); + QRegExp cshSockRx("setenv SSH_AUTH_SOCK (.*);"); + + QRegExp bashPidRx("SSH_AGENT_PID=(\\d*).*"); + QRegExp bashSockRx("SSH_AUTH_SOCK=(.*\\.\\d*);.*"); + + QStringList::Iterator it = m_outputLines.begin(); + QStringList::Iterator end = m_outputLines.end(); + for( ; it != end; ++it ) + { + if( m_pid.isEmpty() ) + { + int pos = cshPidRx.search(*it); + if( pos > -1 ) + { + m_pid = cshPidRx.cap(1); + continue; + } + + pos = bashPidRx.search(*it); + if( pos > -1 ) + { + m_pid = bashPidRx.cap(1); + continue; + } + } + + if( m_authSock.isEmpty() ) + { + int pos = cshSockRx.search(*it); + if( pos > -1 ) + { + m_authSock = cshSockRx.cap(1); + continue; + } + + pos = bashSockRx.search(*it); + if( pos > -1 ) + { + m_authSock = bashSockRx.cap(1); + continue; + } + } + } + + kdDebug(8051) << "SshAgent::slotProcessExited(): pid = " << m_pid + << ", socket = " << m_authSock << endl; +} + + +void SshAgent::slotReceivedStdout(KProcess* proc, char* buffer, int buflen) +{ + Q_UNUSED(proc); + + QString output = QString::fromLocal8Bit(buffer, buflen); + m_outputLines += QStringList::split("\n", output); + + kdDebug(8051) << "SshAgent::slotReceivedStdout(): output = " << output << endl; +} + + +void SshAgent::slotReceivedStderr(KProcess* proc, char* buffer, int buflen) +{ + Q_UNUSED(proc); + + QString output = QString::fromLocal8Bit(buffer, buflen); + m_outputLines += QStringList::split("\n", output); + + kdDebug(8051) << "SshAgent::slotReceivedStderr(): output = " << output << endl; +} + + +bool SshAgent::startSshAgent() +{ + kdDebug(8051) << "SshAgent::startSshAgent(): ENTER" << endl; + + KProcess proc; + + proc << "ssh-agent"; + + connect(&proc, SIGNAL(processExited(KProcess*)), + SLOT(slotProcessExited(KProcess*))); + connect(&proc, SIGNAL(receivedStdout(KProcess*, char*, int)), + SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(&proc, SIGNAL(receivedStderr(KProcess*, char*, int)), + SLOT(slotReceivedStderr(KProcess*, char*, int)) ); + + proc.start(KProcess::NotifyOnExit, KProcess::All); + + // wait for process to finish + // TODO CL use timeout? + proc.wait(); + + return (proc.normalExit() && proc.exitStatus() == 0); +} + + +#include "sshagent.moc" diff --git a/cervisia/cvsservice/sshagent.h b/cervisia/cvsservice/sshagent.h new file mode 100644 index 00000000..894a9026 --- /dev/null +++ b/cervisia/cvsservice/sshagent.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef SSHAGENT_H +#define SSHAGENT_H + +#include <qobject.h> +#include <qstring.h> +#include <qstringlist.h> + +class KProcess; + + +class SshAgent : public QObject +{ + Q_OBJECT + +public: + SshAgent(QObject* parent = 0, const char* name = 0); + ~SshAgent(); + + bool querySshAgent(); + bool addSshIdentities(); + void killSshAgent(); + + bool isRunning() const { return m_isRunning; } + QString pid() const { return m_pid; } + QString authSock() const { return m_authSock; } + +private slots: + void slotProcessExited(KProcess*); + void slotReceivedStdout(KProcess* proc, char* buffer, int buflen); + void slotReceivedStderr(KProcess* proc, char* buffer, int buflen); + +private: + bool startSshAgent(); + + QStringList m_outputLines; + + static bool m_isRunning; + static bool m_isOurAgent; + static QString m_authSock; + static QString m_pid; +}; + + +#endif diff --git a/cervisia/diffdlg.cpp b/cervisia/diffdlg.cpp new file mode 100644 index 00000000..8dcb5bdf --- /dev/null +++ b/cervisia/diffdlg.cpp @@ -0,0 +1,509 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "diffdlg.h" + +#include <qpushbutton.h> +#include <qcheckbox.h> +#include <qcombobox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qkeycode.h> +#include <qfileinfo.h> +#include <qregexp.h> +#include <kconfig.h> +#include <kfiledialog.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <ktempfile.h> +#include <kprocess.h> + +#include "cvsservice_stub.h" +#include "repository_stub.h" +#include "misc.h" +#include "progressdlg.h" +#include "diffview.h" + + +DiffDialog::DiffDialog(KConfig& cfg, QWidget *parent, const char *name, bool modal) + : KDialogBase(parent, name, modal, QString::null, + Close | Help | User1, Close, true, KStdGuiItem::saveAs()) + , partConfig(cfg) +{ + items.setAutoDelete(true); + markeditem = -1; + + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout *layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + QGridLayout *pairlayout = new QGridLayout(layout); + pairlayout->setRowStretch(0, 0); + pairlayout->setRowStretch(1, 1); + pairlayout->setColStretch(1, 0); + pairlayout->addColSpacing(1, 16); + pairlayout->setColStretch(0, 10); + pairlayout->setColStretch(2, 10); + + revlabel1 = new QLabel(mainWidget); + pairlayout->addWidget(revlabel1, 0, 0); + + revlabel2 = new QLabel(mainWidget); + pairlayout->addWidget(revlabel2, 0, 2); + + diff1 = new DiffView(cfg, true, false, mainWidget); + diff2 = new DiffView(cfg, true, true, mainWidget); + DiffZoomWidget *zoom = new DiffZoomWidget(cfg, mainWidget); + zoom->setDiffView(diff2); + + pairlayout->addWidget(diff1, 1, 0); + pairlayout->addWidget(zoom, 1, 1); + pairlayout->addWidget(diff2, 1, 2); + + diff1->setPartner(diff2); + diff2->setPartner(diff1); + + syncbox = new QCheckBox(i18n("Synchronize scroll bars"), mainWidget); + syncbox->setChecked(true); + connect( syncbox, SIGNAL(toggled(bool)), + this, SLOT(toggleSynchronize(bool)) ); + + itemscombo = new QComboBox(mainWidget); + itemscombo->insertItem(QString::null); + connect( itemscombo, SIGNAL(activated(int)), + this, SLOT(comboActivated(int)) ); + + nofnlabel = new QLabel(mainWidget); + // avoids auto resize when the text is changed + nofnlabel->setMinimumWidth(fontMetrics().width(i18n("%1 differences").arg(10000))); + + backbutton = new QPushButton(QString::fromLatin1("&<<"), mainWidget); + connect( backbutton, SIGNAL(clicked()), SLOT(backClicked()) ); + + forwbutton = new QPushButton(QString::fromLatin1("&>>"), mainWidget); + connect( forwbutton, SIGNAL(clicked()), SLOT(forwClicked()) ); + + connect( this, SIGNAL(user1Clicked()), SLOT(saveAsClicked()) ); + + QBoxLayout *buttonlayout = new QHBoxLayout(layout); + buttonlayout->addWidget(syncbox, 0); + buttonlayout->addStretch(4); + buttonlayout->addWidget(itemscombo); + buttonlayout->addStretch(1); + buttonlayout->addWidget(nofnlabel); + buttonlayout->addStretch(1); + buttonlayout->addWidget(backbutton); + buttonlayout->addWidget(forwbutton); + + setHelp("diff"); + + setWFlags(Qt::WDestructiveClose | getWFlags()); + + QSize size = configDialogSize(partConfig, "DiffDialog"); + resize(size); + + KConfigGroupSaver cs(&partConfig, "DiffDialog"); + syncbox->setChecked(partConfig.readBoolEntry("Sync")); +} + + +DiffDialog::~DiffDialog() +{ + saveDialogSize(partConfig, "DiffDialog"); + + KConfigGroupSaver cs(&partConfig, "DiffDialog"); + partConfig.writeEntry("Sync", syncbox->isChecked()); +} + + +void DiffDialog::keyPressEvent(QKeyEvent *e) +{ + switch (e->key()) + { + case Key_Up: + diff1->up(); + diff2->up(); + break; + case Key_Down: + diff1->down(); + diff2->down(); + break; + case Key_Next: + diff1->next(); + diff2->next(); + break; + case Key_Prior: + diff1->prior(); + diff2->prior(); + break; + default: + KDialogBase::keyPressEvent(e); + } +} + + +void DiffDialog::toggleSynchronize(bool b) +{ + diff1->setPartner(b? diff2 : 0); + diff2->setPartner(b? diff1 : 0); +} + + +void DiffDialog::comboActivated(int index) +{ + updateHighlight(index-1); +} + + +static void interpretRegion(QString line, int *linenoA, int *linenoB) +{ + QRegExp region( "^@@ -([0-9]+),([0-9]+) \\+([0-9]+),([0-9]+) @@.*$" ); + + if (!region.exactMatch(line)) + return; + + *linenoA = region.cap(1).toInt() - 1; + *linenoB = region.cap(3).toInt() - 1; +} + + +static QString regionAsString(int linenoA, int linecountA, int linenoB, int linecountB) +{ + int lineendA = linenoA+linecountA-1; + int lineendB = linenoB+linecountB-1; + QString res; + if (linecountB == 0) + res = QString("%1,%2d%3").arg(linenoA).arg(lineendA).arg(linenoB-1); + else if (linecountA == 0) + res = QString("%1a%2,%3").arg(linenoA-1).arg(linenoB).arg(lineendB); + else if (linenoA == lineendA) + if (linenoB == lineendB) + res = QString("%1c%2").arg(linenoA).arg(linenoB); + else + res = QString("%1c%2,%3").arg(linenoA).arg(linenoB).arg(lineendB); + else if (linenoB == lineendB) + res = QString("%1,%2c%3").arg(linenoA).arg(lineendA).arg(linenoB); + else + res = QString("%1,%2c%3,%4").arg(linenoA).arg(lineendA).arg(linenoB).arg(lineendB); + + return res; + +} + + +class DiffItem +{ +public: + DiffView::DiffType type; + int linenoA, linecountA; + int linenoB, linecountB; +}; + + +bool DiffDialog::parseCvsDiff(CvsService_stub* service, const QString& fileName, + const QString &revA, const QString &revB) +{ + QStringList linesA, linesB; + int linenoA, linenoB; + + setCaption(i18n("CVS Diff: %1").arg(fileName)); + revlabel1->setText( revA.isEmpty()? + i18n("Repository:") + : i18n("Revision ")+revA+":" ); + revlabel2->setText( revB.isEmpty()? + i18n("Working dir:") + : i18n("Revision ")+revB+":" ); + + KConfigGroupSaver cs(&partConfig, "General"); + + // Ok, this is a hack: When the user wants an external diff + // front end, it is executed from here. Of course, in that + // case this dialog wouldn't have to be created in the first + // place, but this design at least makes the handling trans- + // parent for the calling routines + + QString extdiff = partConfig.readPathEntry("ExternalDiff"); + if (!extdiff.isEmpty()) + { + callExternalDiff(extdiff, service, fileName, revA, revB); + return false; + } + + const QString diffOptions = partConfig.readEntry("DiffOptions"); + const unsigned contextLines = partConfig.readUnsignedNumEntry("ContextLines", 65535); + + DCOPRef job = service->diff(fileName, revA, revB, diffOptions, contextLines); + if( !service->ok() ) + return false; + + ProgressDialog dlg(this, "Diff", job, "diff", i18n("CVS Diff")); + if( !dlg.execute() ) + return false; + + // remember diff output for "save as" action + m_diffOutput = dlg.getOutput(); + + QString line; + while ( dlg.getLine(line) && !line.startsWith("+++")) + ; + + linenoA = linenoB = 0; + while ( dlg.getLine(line) ) + { + // line contains diff region? + if (line.startsWith("@@")) + { + interpretRegion(line, &linenoA, &linenoB); + diff1->addLine(line, DiffView::Separator); + diff2->addLine(line, DiffView::Separator); + continue; + } + + if (line.length() < 1) + continue; + + QChar marker = line[0]; + line.remove(0, 1); + + if (marker == '-') + linesA.append(line); + else if (marker == '+') + linesB.append(line); + else + { + if (!linesA.isEmpty() || !linesB.isEmpty()) + { + newDiffHunk(linenoA, linenoB, linesA, linesB); + + linesA.clear(); + linesB.clear(); + } + diff1->addLine(line, DiffView::Unchanged, ++linenoA); + diff2->addLine(line, DiffView::Unchanged, ++linenoB); + } + } + + if (!linesA.isEmpty() || !linesB.isEmpty()) + newDiffHunk(linenoA, linenoB, linesA, linesB); + + // sets the right size as there is no more auto resize in QComboBox + itemscombo->adjustSize(); + + updateNofN(); + + return true; +} + + +void DiffDialog::newDiffHunk(int& linenoA, int& linenoB, + const QStringList& linesA, const QStringList& linesB) +{ + DiffItem *item = new DiffItem; + item->linenoA = linenoA+1; + item->linenoB = linenoB+1; + item->linecountA = linesA.count(); + item->linecountB = linesB.count(); + items.append(item); + + const QString region = regionAsString(linenoA+1, linesA.count(), + linenoB+1, linesB.count()); + itemscombo->insertItem(region); + + QStringList::ConstIterator itA = linesA.begin(); + QStringList::ConstIterator itB = linesB.begin(); + while (itA != linesA.end() || itB != linesB.end()) + { + if (itA != linesA.end()) + { + diff1->addLine(*itA, DiffView::Neutral, ++linenoA); + if (itB != linesB.end()) + diff2->addLine(*itB, DiffView::Change, ++linenoB); + else + diff2->addLine("", DiffView::Delete); + } + else + { + diff1->addLine("", DiffView::Neutral); + diff2->addLine(*itB, DiffView::Insert, ++linenoB); + } + + if (itA != linesA.end()) + ++itA; + if (itB != linesB.end()) + ++itB; + } +} + + +void DiffDialog::callExternalDiff(const QString& extdiff, CvsService_stub* service, + const QString& fileName, const QString &revA, + const QString &revB) +{ + QString extcmdline = extdiff; + extcmdline += " "; + + // create suffix for temporary file (used QFileInfo to remove path from file name) + const QString suffix = "-" + QFileInfo(fileName).fileName(); + + DCOPRef job; + if (!revA.isEmpty() && !revB.isEmpty()) + { + // We're comparing two revisions + QString revAFilename = tempFileName(suffix+QString("-")+revA); + QString revBFilename = tempFileName(suffix+QString("-")+revB); + + // download the files for revision A and B + job = service->downloadRevision(fileName, revA, revAFilename, + revB, revBFilename); + if( !service->ok() ) + return; + + extcmdline += KProcess::quote(revAFilename); + extcmdline += " "; + extcmdline += KProcess::quote(revBFilename); + } + else + { + // We're comparing to a file, and perhaps one revision + QString revAFilename = tempFileName(suffix+QString("-")+revA); + job = service->downloadRevision(fileName, revA, revAFilename); + if( !service->ok() ) + return; + + extcmdline += KProcess::quote(revAFilename); + extcmdline += " "; + extcmdline += KProcess::quote(QFileInfo(fileName).absFilePath()); + } + + ProgressDialog dlg(this, "Diff", job, "diff"); + if( dlg.execute() ) + { + // call external diff application + // TODO CL maybe use system()? + KProcess proc; + proc.setUseShell(true, "/bin/sh"); + proc << extcmdline; + proc.start(KProcess::DontCare); + } +} + + +void DiffDialog::updateNofN() +{ + QString str; + if (markeditem >= 0) + str = i18n("%1 of %2").arg(markeditem+1).arg(items.count()); + else + str = i18n("%1 differences").arg(items.count()); + nofnlabel->setText(str); + + itemscombo->setCurrentItem(markeditem==-2? 0 : markeditem+1); + + backbutton->setEnabled(markeditem != -1); + forwbutton->setEnabled(markeditem != -2 && items.count()); +} + + +void DiffDialog::updateHighlight(int newitem) +{ + if (markeditem >= 0) + { + DiffItem *item = items.at(markeditem); + for (int i = item->linenoA; i < item->linenoA+item->linecountA; ++i) + diff1->setInverted(i, false); + for (int i = item->linenoB; i < item->linenoB+item->linecountB; ++i) + diff2->setInverted(i, false); + } + + markeditem = newitem; + + if (markeditem >= 0) + { + DiffItem *item = items.at(markeditem); + for (int i = item->linenoA; i < item->linenoA+item->linecountA; ++i) + diff1->setInverted(i, true); + for (int i = item->linenoB; i < item->linenoB+item->linecountB; ++i) + diff2->setInverted(i, true); + diff1->setCenterLine(item->linenoA); + diff2->setCenterLine(item->linenoB); + } + diff1->repaint(); + diff2->repaint(); + updateNofN(); +} + + +void DiffDialog::backClicked() +{ + int newitem; + if (markeditem == -1) + return; // internal error (button not disabled) + else if (markeditem == -2) // past end + newitem = items.count()-1; + else + newitem = markeditem-1; + updateHighlight(newitem); +} + + +void DiffDialog::forwClicked() +{ + int newitem; + if (markeditem == -2 || (markeditem == -1 && !items.count())) + return; // internal error (button not disabled) + else if (markeditem+1 == (int)items.count()) // past end + newitem = -2; + else + newitem = markeditem+1; + updateHighlight(newitem); +} + + +void DiffDialog::saveAsClicked() +{ + QString fileName = KFileDialog::getSaveFileName(QString::null, QString::null, this); + if( fileName.isEmpty() ) + return; + + if( !Cervisia::CheckOverwrite(fileName, this) ) + return; + + QFile f(fileName); + if( !f.open(IO_WriteOnly) ) + { + KMessageBox::sorry(this, + i18n("Could not open file for writing."), + "Cervisia"); + return; + } + + QTextStream ts(&f); + QStringList::Iterator it = m_diffOutput.begin(); + for( ; it != m_diffOutput.end(); ++it ) + ts << *it << "\n"; + + f.close(); +} + +#include "diffdlg.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/diffdlg.h b/cervisia/diffdlg.h new file mode 100644 index 00000000..25407c96 --- /dev/null +++ b/cervisia/diffdlg.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef DIFFDLG_H +#define DIFFDLG_H + +#include <qptrlist.h> +#include <kdialogbase.h> + + +class QLabel; +class QCheckBox; +class QComboBox; +class KConfig; +class DiffItem; +class DiffView; +class CvsService_stub; + + +class DiffDialog : public KDialogBase +{ + Q_OBJECT + +public: + + explicit DiffDialog( KConfig& config, QWidget *parent=0, const char *name=0, + bool modal=false ); + + virtual ~DiffDialog(); + + bool parseCvsDiff(CvsService_stub* service, const QString &fileName, + const QString &revA, const QString &revB); + +protected: + virtual void keyPressEvent(QKeyEvent *e); + +private slots: + void toggleSynchronize(bool b); + void comboActivated(int index); + void backClicked(); + void forwClicked(); + void saveAsClicked(); + +private: + void newDiffHunk(int& linenoA, int& linenoB, const QStringList& linesA, + const QStringList& linesB); + void callExternalDiff(const QString& extdiff, CvsService_stub* service, + const QString& fileName, const QString& revA, + const QString& revB); + void updateNofN(); + void updateHighlight(int newitem); + + QLabel *revlabel1, *revlabel2, *nofnlabel; + QCheckBox *syncbox; + QComboBox *itemscombo; + QPushButton *backbutton, *forwbutton; + DiffView *diff1, *diff2; + + QPtrList<DiffItem> items; + int markeditem; + KConfig& partConfig; + QStringList m_diffOutput; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/diffview.cpp b/cervisia/diffview.cpp new file mode 100644 index 00000000..1892880e --- /dev/null +++ b/cervisia/diffview.cpp @@ -0,0 +1,507 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "diffview.h" + +#include <qpainter.h> +#include <qscrollbar.h> +#include <qpixmap.h> +#include <qregexp.h> +#include <qstyle.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kglobalsettings.h> +#include <klocale.h> + + +class DiffViewItem +{ +public: + QString line; + DiffView::DiffType type; + bool inverted; + int no; +}; + + +int DiffViewItemList::compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2) +{ + return (static_cast<DiffViewItem*>(item1)->no + == static_cast<DiffViewItem*>(item2)->no)? 0 : 1; +} + + +const int DiffView::BORDER = 7; + + +DiffView::DiffView( KConfig& cfg, bool withlinenos, bool withmarker, + QWidget *parent, const char *name ) + : QtTableView(parent, name, WRepaintNoErase) + , partConfig(cfg) +{ + setNumRows(0); + setNumCols( 1 + (withlinenos?1:0) + (withmarker?1:0) ); + setTableFlags( Tbl_autoVScrollBar|Tbl_autoHScrollBar| + Tbl_smoothVScrolling ); + setFrameStyle( QFrame::WinPanel | QFrame::Sunken ); + setBackgroundMode( PaletteBase ); + setWFlags( WResizeNoErase ); + + partConfig.setGroup("LookAndFeel"); + setFont(partConfig.readFontEntry("DiffFont")); + QFontMetrics fm(font()); + setCellHeight(fm.lineSpacing()); + setCellWidth(0); + textwidth = 0; + + partConfig.setGroup("General"); + m_tabWidth = partConfig.readNumEntry("TabWidth", 8); + + items.setAutoDelete(true); + linenos = withlinenos; + marker = withmarker; + + partConfig.setGroup("Colors"); + QColor defaultColor=QColor(237, 190, 190); + diffChangeColor=partConfig.readColorEntry("DiffChange",&defaultColor); + defaultColor=QColor(190, 190, 237); + diffInsertColor=partConfig.readColorEntry("DiffInsert",&defaultColor); + defaultColor=QColor(190, 237, 190); + diffDeleteColor=partConfig.readColorEntry("DiffDelete",&defaultColor); +} + + +void DiffView::setFont(const QFont &font) +{ + QtTableView::setFont(font); + QFontMetrics fm(font); + setCellHeight(fm.lineSpacing()); +} + + +void DiffView::setPartner(DiffView *other) +{ + partner = other; + if (partner) + { + connect( verticalScrollBar(), SIGNAL(valueChanged(int)), + SLOT(vertPositionChanged(int)) ); + connect( verticalScrollBar(), SIGNAL(sliderMoved(int)), + SLOT(vertPositionChanged(int)) ); + connect( horizontalScrollBar(), SIGNAL(valueChanged(int)), + SLOT(horzPositionChanged(int)) ); + connect( horizontalScrollBar(), SIGNAL(sliderMoved(int)), + SLOT(horzPositionChanged(int)) ); + } +} + + +void DiffView::vertPositionChanged(int val) +{ + if (partner) + partner->setYOffset(QMIN(val,partner->maxYOffset())); +} + + +void DiffView::horzPositionChanged(int val) +{ + if (partner) + partner->setXOffset(QMIN(val,partner->maxXOffset())); +} + + +// *offset methods are only for views withlineno +void DiffView::removeAtOffset(int offset) +{ + items.remove(offset); + setNumRows(numRows()-1); +} + + +void DiffView::insertAtOffset(const QString &line, DiffType type, int offset) +{ + DiffViewItem *item = new DiffViewItem; + item->line = line; + item->type = type; + item->no = -1; + item->inverted = false; + items.insert(offset, item); + setNumRows(numRows()+1); +} + + +void DiffView::setCenterOffset(int offset) +{ + if (!rowIsVisible(offset)) + { + int visiblerows = viewHeight()/cellHeight(0); + setTopCell( QMAX(0, offset - visiblerows/2) ); + } +} + + +void DiffView::addLine(const QString &line, DiffType type, int no) +{ + QFont f(font()); + f.setBold(true); + QFontMetrics fmbold(f); + QFontMetrics fm(font()); + + + // calculate textwidth based on 'line' where tabs are expanded + // + // *Please note* + // For some fonts, e.g. "Clean", is fm.maxWidth() greater than + // fmbold.maxWidth(). + QString copy(line); + const int numTabs = copy.contains('\t', false); + copy.replace( QRegExp("\t"), ""); + + const int tabSize = m_tabWidth * QMAX(fm.maxWidth(), fmbold.maxWidth()); + const int copyWidth = QMAX(fm.width(copy), fmbold.width(copy)); + textwidth = QMAX(textwidth, copyWidth + numTabs * tabSize); + + DiffViewItem *item = new DiffViewItem; + item->line = line; + item->type = type; + item->no = no; + item->inverted = false; + items.append(item); + setNumRows(numRows()+1); +} + + +QString DiffView::stringAtOffset(int offset) +{ + if (offset >= (int)items.count()) + { + kdDebug(8050) << "Internal error: lineAtOffset" << endl; + } + return items.at(offset)->line; +} + + +int DiffView::count() +{ + return items.count(); +} + + +int DiffView::findLine(int lineno) +{ + int offset; + DiffViewItem tmp; + tmp.no = lineno; + if ( (offset = items.find(&tmp)) == -1) + { + kdDebug(8050) << "Internal Error: Line " << lineno << " not found" << endl; + return -1; + } + return offset; +} + + +void DiffView::setInverted(int lineno, bool inverted) +{ + int offset; + if ( (offset = findLine(lineno)) != -1) + items.at(offset)->inverted = inverted; +} + + +void DiffView::setCenterLine(int lineno) +{ + int offset; + if ( (offset = findLine(lineno)) != -1) + setCenterOffset(offset); +} + + +QString DiffView::stringAtLine(int lineno) +{ + int pos; + if ( (pos = findLine(lineno)) != -1 ) + return items.at(pos)->line; + else + return QString(); +} + + +QByteArray DiffView::compressedContent() +{ + QByteArray res(items.count()); + + QPtrListIterator<DiffViewItem> it(items); + int i=0; + for (; it.current(); ++it) + { + switch (it.current()->type) + { + case Change: res[i] = 'C'; break; + case Insert: res[i] = 'I'; break; + case Delete: res[i] = 'D'; break; + case Neutral: res[i] = 'N'; break; + case Unchanged:res[i] = 'U'; break; + default: res[i] = ' '; + } + ++i; + } + return res; +} + + +int DiffView::cellWidth(int col) +{ + if (col == 0 && linenos) + { + QFontMetrics fm(font()); + return fm.width("10000"); + } + else if (marker && (col == 0 || col == 1)) + { + QFontMetrics fm( fontMetrics() ); + return QMAX(QMAX( fm.width(i18n("Delete")), + fm.width(i18n("Insert"))), + fm.width(i18n("Change")))+2*BORDER; + } + else + { + int rest = (linenos || marker)? cellWidth(0) : 0; + if (linenos && marker) + rest += cellWidth(1); + return QMAX(textwidth, viewWidth()-rest); + } +} + + +QSize DiffView::sizeHint() const +{ + QFontMetrics fm(font()); + return QSize( 4*fm.width("0123456789"), fm.lineSpacing()*8 ); +} + + +void DiffView::paintCell(QPainter *p, int row, int col) +{ + QFontMetrics fm(font()); + p->setTabStops(m_tabWidth * fm.maxWidth()); + + DiffViewItem *item = items.at(row); + + int width = cellWidth(col); + int height = cellHeight(); + + QColor backgroundColor; + bool inverted; + int align; + int innerborder; + QString str; + + QFont oldFont(p->font()); + if (item->type==Separator) + { + backgroundColor = KGlobalSettings::highlightColor(); + p->setPen(KGlobalSettings::highlightedTextColor()); + inverted = false; + align = AlignLeft; + innerborder = 0; + if (col == (linenos?1:0) + (marker?1:0)) + str = item->line; + QFont f(oldFont); + f.setBold(true); + p->setFont(f); + } + else if (col == 0 && linenos) + { + backgroundColor = KGlobalSettings::highlightColor(); + p->setPen(KGlobalSettings::highlightedTextColor()); + inverted = false; + align = AlignLeft; + innerborder = 0; + if (item->no == -1) + str = "+++++"; + else + str.setNum(item->no); + } + else if (marker && (col == 0 || col == 1)) + { + backgroundColor = KGlobalSettings::alternateBackgroundColor(); + p->setPen(KGlobalSettings::textColor()); + inverted = false; + align = AlignRight; + innerborder = BORDER; + str = (item->type==Change)? i18n("Change") + : (item->type==Insert)? i18n("Insert") + : (item->type==Delete)? i18n("Delete") : QString::null; + } + else + { + backgroundColor = + (item->type==Change)? diffChangeColor + : (item->type==Insert)? diffInsertColor + : (item->type==Delete)? diffDeleteColor + : (item->type==Neutral)? KGlobalSettings::alternateBackgroundColor() : KGlobalSettings::baseColor(); + p->setPen(KGlobalSettings::textColor()); + inverted = item->inverted; + align = AlignLeft; + innerborder = 0; + str = item->line; + } + + if (inverted) + { + p->setPen(backgroundColor); + backgroundColor = KGlobalSettings::textColor(); + QFont f(oldFont); + f.setBold(true); + p->setFont(f); + } + + p->fillRect(0, 0, width, height, backgroundColor); + p->drawText(innerborder, 0, width-2*innerborder, height, align|ExpandTabs, str); + p->setFont(oldFont); +} + + +void DiffView::wheelEvent(QWheelEvent *e) +{ + QApplication::sendEvent(verticalScrollBar(), e); +} + + +DiffZoomWidget::DiffZoomWidget(KConfig& cfg, QWidget *parent, const char *name) + : QFrame(parent, name) +{ + setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Minimum ) ); + + cfg.setGroup("Colors"); + QColor defaultColor=QColor(237, 190, 190); + diffChangeColor=cfg.readColorEntry("DiffChange",&defaultColor); + defaultColor=QColor(190, 190, 237); + diffInsertColor=cfg.readColorEntry("DiffInsert",&defaultColor); + defaultColor=QColor(190, 237, 190); + diffDeleteColor=cfg.readColorEntry("DiffDelete",&defaultColor); +} + + +DiffZoomWidget::~DiffZoomWidget() +{} + + +void DiffZoomWidget::setDiffView(DiffView *view) +{ + diffview = view; + QScrollBar *sb = const_cast<QScrollBar*>(diffview->scrollBar()); + sb->installEventFilter(this); +} + + +QSize DiffZoomWidget::sizeHint() const +{ + return QSize(25, style().pixelMetric(QStyle::PM_ScrollBarExtent, this)); +} + + +bool DiffZoomWidget::eventFilter(QObject *o, QEvent *e) +{ + if (e->type() == QEvent::Show + || e->type() == QEvent::Hide + || e->type() == QEvent::Resize) + repaint(); + + return QFrame::eventFilter(o, e); +} + + +void DiffZoomWidget::paintEvent(QPaintEvent *) +{ + const QScrollBar* scrollBar = diffview->scrollBar(); + if (!scrollBar) + return; + + // only y and height are important + const QRect scrollBarGroove(scrollBar->isVisible() + ? style().querySubControlMetrics(QStyle::CC_ScrollBar, + scrollBar, + QStyle::SC_ScrollBarGroove) + : rect()); + + // draw rectangles at the positions of the differences + + const QByteArray& lineTypes(diffview->compressedContent()); + + QPixmap pixbuf(width(), scrollBarGroove.height()); + pixbuf.fill(KGlobalSettings::baseColor()); + + QPainter p(&pixbuf, this); + if (const unsigned int numberOfLines = lineTypes.size()) + { + const double scale(((double) scrollBarGroove.height()) / numberOfLines); + for (unsigned int index(0); index < numberOfLines;) + { + const char lineType(lineTypes[index]); + + // don't use qRound() to avoid painting outside of the pixmap + // (yPos1 must be lesser than scrollBarGroove.height()) + const int yPos1(static_cast<int>(index * scale)); + + // search next line with different lineType + for (++index; index < numberOfLines && lineType == lineTypes[index]; ++index) + ; + + QColor color; + switch (lineType) + { + case 'C': + color = diffChangeColor; + break; + case 'I': + color = diffInsertColor; + break; + case 'D': + color = diffDeleteColor; + break; + case ' ': + case 'N': + color = KGlobalSettings::alternateBackgroundColor(); + break; + } + + if (color.isValid()) + { + const int yPos2(qRound(index * scale)); + const int areaHeight((yPos2 != yPos1) ? yPos2 - yPos1 : 1); + + p.fillRect(0, yPos1, pixbuf.width(), areaHeight, QBrush(color)); + } + } + } + p.flush(); + bitBlt(this, 0, scrollBarGroove.y(), &pixbuf); +} + +#include "diffview.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/diffview.h b/cervisia/diffview.h new file mode 100644 index 00000000..b79c7189 --- /dev/null +++ b/cervisia/diffview.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef DIFFVIEW_H +#define DIFFVIEW_H + + +#include "qttableview.h" + +#include <qptrcollection.h> +#include <qptrlist.h> + + +class KConfig; +class DiffViewItem; + + +class DiffViewItemList : public QPtrList<DiffViewItem> +{ +protected: + virtual int compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2); +}; + + +class DiffView : public QtTableView +{ + Q_OBJECT + +public: + enum DiffType { Change, Insert, Delete, Neutral, Unchanged, Separator }; + + DiffView( KConfig& cfg, bool withlinenos, bool withmarker, + QWidget *parent=0, const char *name=0 ); + + void setPartner(DiffView *other); + + void up() + { setTopCell(topCell()-1); } + void down() + { setTopCell(topCell()+1); } + void next() + { setTopCell(topCell()+viewHeight()/cellHeight()); } + void prior() + { setTopCell(topCell()-viewHeight()/cellHeight()); } + + void addLine(const QString &line, DiffType type, int no=-1); + QString stringAtLine(int lineno); + void setCenterLine(int lineno); + void setInverted(int lineno, bool inverted); + int count(); + void removeAtOffset(int offset); + void insertAtOffset(const QString &line, DiffType type, int offset); + void setCenterOffset(int offset); + QString stringAtOffset(int offset); + QByteArray compressedContent(); + + virtual void setFont(const QFont &font); + virtual int cellWidth(int col); + virtual QSize sizeHint() const; + virtual void paintCell(QPainter *p, int row, int col); + virtual void wheelEvent(QWheelEvent *); + const QScrollBar *scrollBar() const + { return verticalScrollBar(); } + +protected slots: + void vertPositionChanged(int val); + void horzPositionChanged(int val); + +private: + int findLine(int lineno); + DiffViewItemList items; + bool linenos; + bool marker; + int textwidth; + DiffView *partner; + static const int BORDER; + + QColor diffChangeColor; + QColor diffInsertColor; + QColor diffDeleteColor; + + int m_tabWidth; + KConfig& partConfig; +}; + + +class DiffZoomWidget : public QFrame +{ + Q_OBJECT + +public: + DiffZoomWidget(KConfig& cfg, QWidget *parent=0, const char *name=0); + ~DiffZoomWidget(); + + void setDiffView(DiffView *view); + QSize sizeHint() const; + +protected: + void paintEvent(QPaintEvent *); + bool eventFilter(QObject *, QEvent *e); + +private: + DiffView *diffview; + + QColor diffChangeColor; + QColor diffInsertColor; + QColor diffDeleteColor; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/dirignorelist.cpp b/cervisia/dirignorelist.cpp new file mode 100644 index 00000000..d6ffcf2d --- /dev/null +++ b/cervisia/dirignorelist.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "dirignorelist.h" +using namespace Cervisia; + +#include <qfileinfo.h> + + +DirIgnoreList::DirIgnoreList(const QString& path) +{ + addEntriesFromFile(path + "/.cvsignore"); +} + + +void DirIgnoreList::addEntry(const QString& entry) +{ + if (entry != QChar('!')) + { + m_stringMatcher.add(entry); + } + else + { + m_stringMatcher.clear(); + } +} + + +bool DirIgnoreList::matches(const QFileInfo* fi) const +{ + return m_stringMatcher.match(fi->fileName()); +} diff --git a/cervisia/dirignorelist.h b/cervisia/dirignorelist.h new file mode 100644 index 00000000..41f82982 --- /dev/null +++ b/cervisia/dirignorelist.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef CERVISIA_DIRIGNORELIST_H +#define CERVISIA_DIRIGNORELIST_H + +#include "ignorelistbase.h" +#include "stringmatcher.h" + +class QFileInfo; + + +namespace Cervisia +{ + + +/* Encapsulates the .cvsignore file inside a CVS-controlled directory. */ +class DirIgnoreList : public IgnoreListBase +{ +public: + explicit DirIgnoreList(const QString& path); + + virtual bool matches(const QFileInfo* fi) const; + +private: + virtual void addEntry(const QString& entry); + + StringMatcher m_stringMatcher; +}; + + +} + + +#endif diff --git a/cervisia/editwithmenu.cpp b/cervisia/editwithmenu.cpp new file mode 100644 index 00000000..9ca0f14b --- /dev/null +++ b/cervisia/editwithmenu.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "editwithmenu.h" +using namespace Cervisia; + +#include <qpopupmenu.h> +#include <kdebug.h> +#include <kiconloader.h> +#include <kmimetype.h> +#include <krun.h> +#include <kurl.h> + + +EditWithMenu::EditWithMenu(const KURL& url, QWidget* parent) + : QObject(parent) + , m_menu(0) + , m_url(url) +{ + KMimeType::Ptr type = KMimeType::findByURL(url, 0, true); + if( type->name() == KMimeType::defaultMimeType() ) + { + kdDebug() << "Couldn't find mime type!" << endl; + return; + } + + m_offers = KTrader::self()->query(type->name(), "Type == 'Application'"); + + if( !m_offers.isEmpty() ) + { + m_menu = new QPopupMenu(); + + KTrader::OfferList::ConstIterator it = m_offers.begin(); + for( int i = 0 ; it != m_offers.end(); ++it, ++i ) + { + int id = m_menu->insertItem(SmallIcon((*it)->icon()), + (*it)->name(), + this, SLOT(itemActivated(int))); + m_menu->setItemParameter(id, i); + } + } +} + + +QPopupMenu* EditWithMenu::menu() +{ + return m_menu; +} + + +void EditWithMenu::itemActivated(int item) +{ + KService::Ptr service = m_offers[item]; + + KURL::List list; + list.append(m_url); + + KRun::run(*service, list); +} + + +#include "editwithmenu.moc" + diff --git a/cervisia/editwithmenu.h b/cervisia/editwithmenu.h new file mode 100644 index 00000000..f78b7a2e --- /dev/null +++ b/cervisia/editwithmenu.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef EDITWITHMENU_H +#define EDITWITHMENU_H + +#include <qobject.h> +#include <ktrader.h> +#include <kurl.h> + +class QPopupMenu; + + +namespace Cervisia +{ + + +class EditWithMenu : public QObject +{ + Q_OBJECT + +public: + EditWithMenu(const KURL& url, QWidget* parent); + QPopupMenu* menu(); + +private slots: + void itemActivated(int); + +private: + KTrader::OfferList m_offers; + QPopupMenu* m_menu; + KURL m_url; +}; + + +} + + +#endif + diff --git a/cervisia/entry.cpp b/cervisia/entry.cpp new file mode 100644 index 00000000..3cda036d --- /dev/null +++ b/cervisia/entry.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "entry.h" + + +namespace Cervisia +{ + + +Entry::Entry() + : m_type(File), + m_status(Unknown) +{ +} + + +} // namespace Cervisia diff --git a/cervisia/entry.h b/cervisia/entry.h new file mode 100644 index 00000000..875885d0 --- /dev/null +++ b/cervisia/entry.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef CERVISIA_ENTRY_H +#define CERVISIA_ENTRY_H + + +#include <qdatetime.h> +#include <qstring.h> + +#include "entry_status.h" + + +namespace Cervisia +{ + + +/** + * Dumb data struct to store an entry controlled by a version control system. + */ +struct Entry +{ + enum Type + { + Dir, + File + }; + + /** + * Sets status to \a EntryStatus::Unknown and type to \a File. + */ + Entry(); + + /** + * The name of this entry (without path). + */ + QString m_name; + + /** + * The type of this entry. + */ + Type m_type; + + /** + * The status of this entry. + */ + EntryStatus m_status; + + /** + * The revision of this entry. + */ + QString m_revision; + + /** + * The modification date/time of this entry (in user's local time). + */ + QDateTime m_dateTime; + + /** + * The tag/branch of this entry. + */ + QString m_tag; +}; + + +} // namespace Cervisia + + +#endif // CERVISIA_ENTRY_H diff --git a/cervisia/entry_status.cpp b/cervisia/entry_status.cpp new file mode 100644 index 00000000..8ad624fa --- /dev/null +++ b/cervisia/entry_status.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2004-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "entry_status.h" + +#include <qstring.h> + +#include <klocale.h> + + +namespace Cervisia +{ + + +QString toString(EntryStatus entryStatus) +{ + QString result; + switch (entryStatus) + { + case LocallyModified: + result = i18n("Locally Modified"); + break; + case LocallyAdded: + result = i18n("Locally Added"); + break; + case LocallyRemoved: + result = i18n("Locally Removed"); + break; + case NeedsUpdate: + result = i18n("Needs Update"); + break; + case NeedsPatch: + result = i18n("Needs Patch"); + break; + case NeedsMerge: + result = i18n("Needs Merge"); + break; + case UpToDate: + result = i18n("Up to Date"); + break; + case Conflict: + result = i18n("Conflict"); + break; + case Updated: + result = i18n("Updated"); + break; + case Patched: + result = i18n("Patched"); + break; + case Removed: + result = i18n("Removed"); + break; + case NotInCVS: + result = i18n("Not in CVS"); + break; + case Unknown: + result = i18n("Unknown"); + break; + } + + return result; +} + + +} // namespace Cervisia diff --git a/cervisia/entry_status.h b/cervisia/entry_status.h new file mode 100644 index 00000000..634e0a04 --- /dev/null +++ b/cervisia/entry_status.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2004-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef CERVISIA_ENTRY_STATUS_H +#define CERVISIA_ENTRY_STATUS_H + + +class QString; + + +namespace Cervisia +{ + + +/** + * All stati a an entry could have. + */ +enum EntryStatus +{ + LocallyModified, + LocallyAdded, + LocallyRemoved, + NeedsUpdate, + NeedsPatch, + NeedsMerge, + UpToDate, + Conflict, + Updated, + Patched, + Removed, + NotInCVS, + Unknown +}; + +/** + * The entry status as translated string. + * + * @param entryStatus The entry status to translate. + * + * @return The translated string. + */ +QString toString(EntryStatus entryStatus); + + +} // namespace Cervisia + + +#endif // CERVISIA_ENTRY_STATUS_H diff --git a/cervisia/entry_status_change.h b/cervisia/entry_status_change.h new file mode 100644 index 00000000..63b5f089 --- /dev/null +++ b/cervisia/entry_status_change.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2004-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef CERVISIA_ENTRY_STATUS_CHANGE_H +#define CERVISIA_ENTRY_STATUS_CHANGE_H + + +#include <qstring.h> + +#include "entry_status.h" + + +namespace Cervisia +{ + + +/** + * Dumb data struct to store a status change of an entry (i.e. for jobs like + * status, update, ...). + */ +struct EntryStatusChange +{ + /** + * The name of the changed entry (including the path inside the repository / working copy). + */ + QString m_name; + + /** + * The new status of the entry. + */ + EntryStatus m_status; +}; + + +} // namespace Cervisia + + +#endif // CERVISIA_ENTRY_STATUS_CHANGE_H diff --git a/cervisia/eventsrc b/cervisia/eventsrc new file mode 100644 index 00000000..cc88f561 --- /dev/null +++ b/cervisia/eventsrc @@ -0,0 +1,88 @@ +[!Global!] +IconName=cervisia +Comment=Cervisia + +[cvs_commit_done] +Name=CVS commit job done +Name[bg]=Изпращането в CVS е завършено +Name[ca]=Entrega al CVS completada +Name[cs]=CVS commit úloha dokončena +Name[da]=CVS-indsending udført +Name[de]=CVS-Einspielvorgang ausgeführt +Name[el]=Έγινε η εργασία καταχώρησης CVS +Name[es]=Entrega al CVS completada +Name[et]=CVS sissekanne tehtud +Name[eu]=CVS aldaketak egin dira +Name[fa]=کار تصدیق CVS انجام شد +Name[fi]=CVS-toimitustyö tehty +Name[fr]=Validation CVS effectuée +Name[ga]=Athruithe curtha i bhfeidhm ag CVS +Name[gl]=Entrega CVS finalizada +Name[hu]=CVS commit művelet kész +Name[is]=CVS innsetningu lokið +Name[it]=Processo di consegna a CVS eseguito +Name[ja]=CVS コミットは終了しました。 +Name[ka]=CVS შესრულებული დავალება შესრულდა +Name[kk]=CVS тапсыру жұмысын аяқтады +Name[lt]=CVS įkėlimo veiksmas atliktas +Name[nb]=CVS-innmeldingsjobb er gjort +Name[nds]=CVS-Inspeelopgaav is fardig +Name[ne]=CVS commit कार्य समाप्त +Name[nl]=CVS vastleggen (commit) voltooid +Name[nn]=CVS-innmeldingsjobb er gjort +Name[pa]=CVS ਕਮਿਟ ਕੰਮ ਮੁਕੰਮਲ +Name[pl]=Wysyłanie do repozytorium CVS zakończone +Name[pt]=Trabalho de 'commit' de CVS terminado +Name[pt_BR]=Trabalho de envio para o CVS feito +Name[ru]=CVS Commit выполнен +Name[sk]=CVS potvrdzujúca úloha ukončená +Name[sl]=Opravilo udejanjanja v CVS končano +Name[sr]=CVS предаја завршена +Name[sr@Latn]=CVS predaja završena +Name[sv]=CVS-arkiveringsjobb klart +Name[tr]=CVS teslim etme işi tamamlandı +Name[uk]=Завдання передачі CVS виконане +Name[zh_CN]=CVS 提交任务已完成 +Name[zh_TW]=CVS 提交工作完成 +Comment=A CVS commit job is done +Comment[bg]=Изпращането в CVS е завършено +Comment[ca]=S'ha completat una entrega al CVS +Comment[cs]=CVS commit úloha je dokončena +Comment[da]=En CVS-indsending er udført +Comment[de]=Ein CVS-Einspielvorgang wurde ausgeführt +Comment[el]=Μια εργασία καταχώρησης CVS έγινε +Comment[es]=Se ha completado una entrega al CVS +Comment[et]=CVS sissekanne tehtud +Comment[eu]=CVS aldaketa egin da +Comment[fa]=کار تصدیق CVS انجام میشود. +Comment[fi]=CVS-toimitustyö on tehty +Comment[fr]=Une validation CVS a été effectué +Comment[ga]=Athruithe curtha i bhfeidhm ag CVS +Comment[gl]=Unha entrega CVS finalizou +Comment[hu]=Egy CVS commit művelet sikeresen befejeződött +Comment[is]=CVS innsetningu lokið +Comment[it]=Un processo di deposito su CVS è stato eseguito +Comment[ja]=CVS コミットは終了しました。 +Comment[ka]=CVS შესრულებული დავალება შესრულდა +Comment[kk]=CVS тапсыру жұмысы аяқталды +Comment[lt]=CVS įkėlimo veiksmas atliktas +Comment[nb]=En CVS-innmeldingsjobb er utført +Comment[nds]=En CVS-Inspeelopgaav is fardig +Comment[ne]=CVS commit कार्य समाप्त भएको छ +Comment[nl]=Het vastleggen in CVS (commit) is voltooid +Comment[nn]=Ein CVS-innmeldingsjobb er gjort +Comment[pa]=ਇੱਕ CVS ਕਮਿਟ ਕੰਮ ਖਤਮ ਹੋਇਆ +Comment[pl]=Wysyłanie do repozytorium CVS zostało zakończone +Comment[pt]=Um trabalho de 'commit' de CVS terminou +Comment[pt_BR]=Um trabalho de envio para o CVS foi feito +Comment[ru]=CVS Commit выполнен +Comment[sk]=CVS potvrdzujúca úloha ukončená +Comment[sl]=Opravilo udejanjanja v CVS je končano +Comment[sr]=Посао CVS предаје је завршен +Comment[sr@Latn]=Posao CVS predaje je završen +Comment[sv]=Ett CVS-arkiveringsjobb är klart +Comment[tr]=Bir CVS teslim etme işi tamamlandı +Comment[uk]=Завдання передачі CVS виконане +Comment[zh_CN]=CVS 提交任务已完成 +Comment[zh_TW]=CVS 提交工作完成 +default_presentation=0 diff --git a/cervisia/globalignorelist.cpp b/cervisia/globalignorelist.cpp new file mode 100644 index 00000000..6d905fc7 --- /dev/null +++ b/cervisia/globalignorelist.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "globalignorelist.h" +using namespace Cervisia; + +#include <qdir.h> +#include <kdebug.h> +#include <ktempfile.h> +#include <stdlib.h> // for getenv() + +#include "cvsservice_stub.h" +#include "progressdlg.h" + + +StringMatcher GlobalIgnoreList::m_stringMatcher; +bool GlobalIgnoreList::m_isInitialized = false; + + +GlobalIgnoreList::GlobalIgnoreList() +{ + if( !m_isInitialized ) + setup(); +} + + +bool GlobalIgnoreList::matches(const QFileInfo* fi) const +{ + return m_stringMatcher.match(fi->fileName()); +} + + +void GlobalIgnoreList::retrieveServerIgnoreList(CvsService_stub* cvsService, + const QString& repository) +{ + KTempFile tmpFile; + tmpFile.setAutoDelete(true); + + // clear old ignore list + m_stringMatcher.clear(); + + // now set it up again + setup(); + + DCOPRef ref = cvsService->downloadCvsIgnoreFile(repository, + tmpFile.name()); + + ProgressDialog dlg(0, "Edit", ref, "checkout", "CVS Edit"); + if( !dlg.execute() ) + return; + + addEntriesFromFile(tmpFile.name()); +} + + +void GlobalIgnoreList::addEntry(const QString& entry) +{ + if (entry != QChar('!')) + { + m_stringMatcher.add(entry); + } + else + { + m_stringMatcher.clear(); + + // Bug #89215: + // Make sure '.' and '..' are always in the ignore list, so + // UpdateDirItem::maybeScanDir() doesn't loop endlessly. + addEntriesFromString(QString::fromLatin1(". ..")); + } +} + + +void GlobalIgnoreList::setup() +{ + static const char ignorestr[] = ". .. core RCSLOG tags TAGS RCS SCCS .make.state\ +.nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj\ +*.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$"; + + addEntriesFromString(QString::fromLatin1(ignorestr)); + addEntriesFromString(QString::fromLocal8Bit(::getenv("CVSIGNORE"))); + addEntriesFromFile(QDir::homeDirPath() + "/.cvsignore"); + + m_isInitialized = true; +} diff --git a/cervisia/globalignorelist.h b/cervisia/globalignorelist.h new file mode 100644 index 00000000..b8f72664 --- /dev/null +++ b/cervisia/globalignorelist.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef CERVISIA_GLOBALIGNORELIST_H +#define CERVISIA_GLOBALIGNORELIST_H + +#include "ignorelistbase.h" +#include "stringmatcher.h" + +class QFileInfo; +class CvsService_stub; + + +namespace Cervisia +{ + + +class GlobalIgnoreList : public IgnoreListBase +{ +public: + GlobalIgnoreList(); + + virtual bool matches(const QFileInfo* fi) const; + + void retrieveServerIgnoreList(CvsService_stub* cvsService, + const QString& repository); + +private: + void setup(); + virtual void addEntry(const QString& entry); + + static StringMatcher m_stringMatcher; + static bool m_isInitialized; +}; + + +} + + +#endif diff --git a/cervisia/hi16-app-cervisia.png b/cervisia/hi16-app-cervisia.png Binary files differnew file mode 100644 index 00000000..3850214d --- /dev/null +++ b/cervisia/hi16-app-cervisia.png diff --git a/cervisia/hi22-app-cervisia.png b/cervisia/hi22-app-cervisia.png Binary files differnew file mode 100644 index 00000000..253ec4a0 --- /dev/null +++ b/cervisia/hi22-app-cervisia.png diff --git a/cervisia/hi32-app-cervisia.png b/cervisia/hi32-app-cervisia.png Binary files differnew file mode 100644 index 00000000..1eda7cb8 --- /dev/null +++ b/cervisia/hi32-app-cervisia.png diff --git a/cervisia/hi48-app-cervisia.png b/cervisia/hi48-app-cervisia.png Binary files differnew file mode 100644 index 00000000..309cc49f --- /dev/null +++ b/cervisia/hi48-app-cervisia.png diff --git a/cervisia/historydlg.cpp b/cervisia/historydlg.cpp new file mode 100644 index 00000000..35eb5e01 --- /dev/null +++ b/cervisia/historydlg.cpp @@ -0,0 +1,397 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "historydlg.h" + +#include <qcheckbox.h> +#include <qdatetime.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qregexp.h> +#include <kconfig.h> +#include <klineedit.h> +#include <klistview.h> +#include <klocale.h> +#include <krfcdate.h> + +#include "misc.h" +#include "cvsservice_stub.h" +#include "progressdlg.h" + + +static QDateTime parseDate(const QString& date, const QString& _time, const QString& offset) +{ + // cvs history only prints hh:mm but parseDateISO8601 needs hh:mm:ss + QString time(_time); + if( time.contains(':') == 1 ) + time += ":00"; + + QDateTime dateTime; + dateTime.setTime_t(KRFCDate::parseDateISO8601(date + 'T' + time + offset)); + + return dateTime; +} + + +class HistoryItem : public QListViewItem +{ +public: + + enum { Date, Event, Author, Revision, File, Path }; + + HistoryItem(QListView *parent, const QDateTime& date) + : QListViewItem(parent), m_date(date) + {} + + virtual int compare(QListViewItem* i, int col, bool) const; + + virtual QString text(int col) const; + + bool isCommit(); + bool isCheckout(); + bool isTag(); + bool isOther(); + +private: + + const QDateTime m_date; +}; + + +int HistoryItem::compare(QListViewItem* i, int col, bool ascending) const +{ + const HistoryItem* pItem = static_cast<HistoryItem*>(i); + + int iResult; + switch (col) + { + case Date: + iResult = ::compare(m_date, pItem->m_date); + break; + case Revision: + iResult = ::compareRevisions(text(Revision), pItem->text(Revision)); + break; + default: + iResult = QListViewItem::compare(i, col, ascending); + } + + return iResult; +} + + +QString HistoryItem::text(int col) const +{ + QString sText; + switch (col) + { + case Date: + sText = KGlobal::locale()->formatDateTime(m_date); + break; + default: + sText = QListViewItem::text(col); + } + + return sText; +} + + +bool HistoryItem::isCommit() +{ + return text(1) == i18n("Commit, Modified ") + || text(1) == i18n("Commit, Added ") + || text(1) == i18n("Commit, Removed "); +} + + +bool HistoryItem::isCheckout() +{ + return text(1) == i18n("Checkout "); +} + + +bool HistoryItem::isTag() +{ + return text(1) == i18n("Tag"); +} + + +bool HistoryItem::isOther() +{ + return !isCommit() && !isCheckout() && !isTag(); +} + + +HistoryDialog::HistoryDialog(KConfig& cfg, QWidget *parent, const char *name) + : KDialogBase(parent, name, false, QString::null, + Close | Help, ButtonCode(0), true) + , partConfig(cfg) +{ + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout *layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + listview = new KListView(mainWidget); + listview->setSelectionMode(QListView::NoSelection); + listview->setAllColumnsShowFocus(true); + listview->setShowSortIndicator(true); + listview->setSorting(HistoryItem::Date, false); + listview->addColumn(i18n("Date")); + listview->addColumn(i18n("Event")); + listview->addColumn(i18n("Author")); + listview->addColumn(i18n("Revision")); + listview->addColumn(i18n("File")); + listview->addColumn(i18n("Repo Path")); + listview->setFocus(); + layout->addWidget(listview, 1); + + commit_box = new QCheckBox(i18n("Show c&ommit events"), mainWidget); + commit_box->setChecked(true); + + checkout_box = new QCheckBox(i18n("Show ch&eckout events"), mainWidget); + checkout_box->setChecked(true); + + tag_box = new QCheckBox(i18n("Show &tag events"), mainWidget); + tag_box->setChecked(true); + + other_box = new QCheckBox(i18n("Show &other events"), mainWidget); + other_box->setChecked(true); + + onlyuser_box = new QCheckBox(i18n("Only &user:"), mainWidget); + + onlyfilenames_box = new QCheckBox(i18n("Only &filenames matching:"), mainWidget); + + onlydirnames_box = new QCheckBox(i18n("Only &folders matching:"), mainWidget); + + user_edit = new KLineEdit(mainWidget); + user_edit->setEnabled(false); + + filename_edit = new KLineEdit(mainWidget); + filename_edit->setEnabled(false); + + dirname_edit = new KLineEdit(mainWidget); + dirname_edit->setEnabled(false); + + connect( onlyuser_box, SIGNAL(toggled(bool)), + this, SLOT(toggled(bool)) ); + connect( onlyfilenames_box, SIGNAL(toggled(bool)), + this, SLOT(toggled(bool)) ); + connect( onlydirnames_box, SIGNAL(toggled(bool)), + this, SLOT(toggled(bool)) ); + connect( commit_box, SIGNAL(toggled(bool)), + this, SLOT(choiceChanged()) ); + connect( checkout_box, SIGNAL(toggled(bool)), + this, SLOT(choiceChanged()) ); + connect( tag_box, SIGNAL(toggled(bool)), + this, SLOT(choiceChanged()) ); + connect( other_box, SIGNAL(toggled(bool)), + this, SLOT(choiceChanged()) ); + connect( onlyuser_box, SIGNAL(toggled(bool)), + this, SLOT(choiceChanged()) ); + connect( onlyfilenames_box, SIGNAL(toggled(bool)), + this, SLOT(choiceChanged()) ); + connect( onlydirnames_box, SIGNAL(toggled(bool)), + this, SLOT(choiceChanged()) ); + connect( user_edit, SIGNAL(returnPressed()), + this, SLOT(choiceChanged()) ); + connect( filename_edit, SIGNAL(returnPressed()), + this, SLOT(choiceChanged()) ); + connect( dirname_edit, SIGNAL(returnPressed()), + this, SLOT(choiceChanged()) ); + + QGridLayout *grid = new QGridLayout(layout); + grid->setColStretch(0, 1); + grid->setColStretch(1, 0); + grid->setColStretch(2, 4); + grid->setColStretch(3, 1); + grid->addWidget(commit_box, 0, 0); + grid->addWidget(checkout_box, 1, 0); + grid->addWidget(tag_box, 2, 0); + grid->addWidget(other_box, 3, 0); + grid->addWidget(onlyuser_box, 0, 1); + grid->addWidget(user_edit, 0, 2); + grid->addWidget(onlyfilenames_box, 1, 1); + grid->addWidget(filename_edit, 1, 2); + grid->addWidget(onlydirnames_box, 2, 1); + grid->addWidget(dirname_edit, 2, 2); + + // no default button because "return" is needed to activate the filters (line edits) + actionButton(Help)->setAutoDefault(false); + actionButton(Close)->setAutoDefault(false); + + setHelp("browsinghistory"); + + setWFlags(Qt::WDestructiveClose | getWFlags()); + + QSize size = configDialogSize(partConfig, "HistoryDialog"); + resize(size); + + // without this restoreLayout() can't change the column widths + for (int i = 0; i < listview->columns(); ++i) + listview->setColumnWidthMode(i, QListView::Manual); + + listview->restoreLayout(&partConfig, QString::fromLatin1("HistoryListView")); +} + + +HistoryDialog::~HistoryDialog() +{ + saveDialogSize(partConfig, "HistoryDialog"); + + listview->saveLayout(&partConfig, QString::fromLatin1("HistoryListView")); +} + + +void HistoryDialog::choiceChanged() +{ + const QString author(user_edit->text()); + const QRegExp fileMatcher(filename_edit->text(), true, true); + const QRegExp pathMatcher(dirname_edit->text(), true, true); + + const bool showCommitEvents(commit_box->isChecked()); + const bool showCheckoutEvents(checkout_box->isChecked()); + const bool showTagEvents(tag_box->isChecked()); + const bool showOtherEvents(other_box->isChecked()); + const bool filterByAuthor(onlyuser_box->isChecked() && !author.isEmpty()); + const bool filterByFile(onlyfilenames_box->isChecked() && !fileMatcher.isEmpty()); + const bool filterByPath(onlydirnames_box->isChecked() && !pathMatcher.isEmpty()); + + QListViewItemIterator it(listview); + for (; it.current(); ++it) + { + HistoryItem *item = static_cast<HistoryItem*>(it.current()); + + bool visible( (showCommitEvents && item->isCommit()) + || (showCheckoutEvents && item->isCheckout()) + || (showTagEvents && item->isTag()) + || (showOtherEvents && item->isOther())); + visible = visible + && (!filterByAuthor || author == item->text(HistoryItem::Author)) + && (!filterByFile || fileMatcher.search(item->text(HistoryItem::File)) >= 0) + && (!filterByPath || pathMatcher.search(item->text(HistoryItem::Path)) >= 0); + + item->setVisible(visible); + } +} + + +void HistoryDialog::toggled(bool b) +{ + KLineEdit *edit = 0; + + if (sender() == onlyuser_box) + edit = user_edit; + else if (sender() == onlyfilenames_box) + edit = filename_edit; + else if (sender() == onlydirnames_box) + edit = dirname_edit; + + edit->setEnabled(b); + if (b) + edit->setFocus(); +} + + +bool HistoryDialog::parseHistory(CvsService_stub* cvsService) +{ + setCaption(i18n("CVS History")); + + DCOPRef job = cvsService->history(); + if( !cvsService->ok() ) + return false; + + ProgressDialog dlg(this, "History", job, "history", i18n("CVS History")); + if( !dlg.execute() ) + return false; + + QString line; + while( dlg.getLine(line) ) + { + const QStringList list(splitLine(line)); + const int listSize(list.size()); + if( listSize < 6) + continue; + + QString cmd = list[0]; + if( cmd.length() != 1 ) + continue; + + int ncol; + int cmd_code = cmd[0].latin1(); + switch (cmd_code) + { + case 'O': + case 'F': + case 'E': + ncol = 8; + break; + default: + ncol = 10; + break; + } + + if( ncol != (int)list.count() ) + continue; + + QString event; + switch( cmd_code ) + { + case 'O': event = i18n("Checkout "); break; + case 'T': event = i18n("Tag "); break; + case 'F': event = i18n("Release "); break; + case 'W': event = i18n("Update, Deleted "); break; + case 'U': event = i18n("Update, Copied "); break; + case 'G': event = i18n("Update, Merged "); break; + case 'C': event = i18n("Update, Conflict "); break; + case 'P': event = i18n("Update, Patched "); break; + case 'M': event = i18n("Commit, Modified "); break; + case 'A': event = i18n("Commit, Added "); break; + case 'R': event = i18n("Commit, Removed "); break; + default: event = i18n("Unknown "); + } + + const QDateTime date(parseDate(list[1], list[2], list[3])); + + HistoryItem *item = new HistoryItem(listview, date); + item->setText(HistoryItem::Event, event); + item->setText(HistoryItem::Author, list[4]); + if( ncol == 10 ) + { + item->setText(HistoryItem::Revision, list[5]); + if( listSize >= 8 ) + { + item->setText(HistoryItem::File, list[6]); + item->setText(HistoryItem::Path, list[7]); + } + } + else + { + item->setText(HistoryItem::Path, list[5]); + } + } + + return true; +} + +#include "historydlg.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/historydlg.h b/cervisia/historydlg.h new file mode 100644 index 00000000..ac2241d2 --- /dev/null +++ b/cervisia/historydlg.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef HISTORYDLG_H +#define HISTORYDLG_H + + +#include <kdialogbase.h> + + +class QCheckBox; +class KConfig; +class KLineEdit; +class KListView; +class CvsService_stub; + + +class HistoryDialog : public KDialogBase +{ + Q_OBJECT + +public: + explicit HistoryDialog( KConfig& cfg, QWidget *parent=0, const char *name=0 ); + virtual ~HistoryDialog(); + + bool parseHistory(CvsService_stub* cvsService); + +private slots: + void choiceChanged(); + void toggled(bool b); + +private: + KListView *listview; + QCheckBox *commit_box, *checkout_box, *tag_box, *other_box; + QCheckBox *onlyuser_box, *onlyfilenames_box, *onlydirnames_box; + KLineEdit *user_edit, *filename_edit, *dirname_edit; + KConfig& partConfig; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/ignorelistbase.cpp b/cervisia/ignorelistbase.cpp new file mode 100644 index 00000000..27b3d159 --- /dev/null +++ b/cervisia/ignorelistbase.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "ignorelistbase.h" +using namespace Cervisia; + +#include <qfile.h> +#include <qstringlist.h> +#include <qtextstream.h> + + +void IgnoreListBase::addEntriesFromString(const QString& str) +{ + QStringList entries = QStringList::split(' ', str); + for( QStringList::iterator it = entries.begin(); it != entries.end(); ++it ) + { + addEntry(*it); + } +} + + +void IgnoreListBase::addEntriesFromFile(const QString& name) +{ + QFile file(name); + + if( file.open(IO_ReadOnly) ) + { + QTextStream stream(&file); + while( !stream.eof() ) + { + addEntriesFromString(stream.readLine()); + } + } +} diff --git a/cervisia/ignorelistbase.h b/cervisia/ignorelistbase.h new file mode 100644 index 00000000..9d166796 --- /dev/null +++ b/cervisia/ignorelistbase.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef CERVISIA_IGNORELISTBASE_H +#define CERVISIA_IGNORELISTBASE_H + +class QFileInfo; +class QString; + + +namespace Cervisia +{ + + +class IgnoreListBase +{ +public: + virtual ~IgnoreListBase() {} + + virtual bool matches(const QFileInfo* fi) const = 0; + +protected: + void addEntriesFromString(const QString& str); + void addEntriesFromFile(const QString& name); + +private: + virtual void addEntry(const QString& entry) = 0; +}; + + +} + + +#endif diff --git a/cervisia/logdlg.cpp b/cervisia/logdlg.cpp new file mode 100644 index 00000000..41a0751c --- /dev/null +++ b/cervisia/logdlg.cpp @@ -0,0 +1,620 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "logdlg.h" + +#include <qcombobox.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qtabwidget.h> +#include <qtextedit.h> +#include <qwhatsthis.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kfiledialog.h> +#include <kfinddialog.h> +#include <kglobalsettings.h> +#include <kiconloader.h> +#include <klistviewsearchline.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kprocess.h> +#include <krfcdate.h> +#include <krun.h> +#include <kurl.h> + +#include "cvsservice_stub.h" +#include "annotatedlg.h" +#include "annotatectl.h" +#include "diffdlg.h" +#include "loginfo.h" +#include "loglist.h" +#include "logplainview.h" +#include "logtree.h" +#include "misc.h" +#include "progressdlg.h" +#include "patchoptiondlg.h" + + +LogDialog::LogDialog(KConfig& cfg, QWidget *parent, const char *name) + : KDialogBase(parent, name, false, QString::null, + Ok | Apply | Close | Help | User1 | User2 | User3, Close, true, + KGuiItem(i18n("&Annotate")), + KGuiItem(i18n("&Diff"), "vcs_diff"), + KGuiItem(i18n("&Find..."), "find")) + , cvsService(0) + , partConfig(cfg) +{ + QSplitter *splitter = new QSplitter(Qt::Vertical, this); + setMainWidget(splitter); + + tree = new LogTreeView(this); + connect( tree, SIGNAL(revisionClicked(QString,bool)), + this, SLOT(revisionSelected(QString,bool)) ); + + QWidget* listWidget = new QWidget(this); + QVBoxLayout* listLayout = new QVBoxLayout(listWidget); + QHBoxLayout* searchLayout = new QHBoxLayout(listLayout); + searchLayout->setMargin(KDialog::spacingHint()); + searchLayout->setSpacing(KDialog::spacingHint()); + + list = new LogListView(partConfig, listWidget); + listLayout->addWidget(list, 1); + + KListViewSearchLine* searchLine = new KListViewSearchLine(listWidget, list); + QLabel* searchLabel = new QLabel(searchLine, i18n("S&earch:"), listWidget); + searchLayout->addWidget(searchLabel); + searchLayout->addWidget(searchLine, 1); + + connect( list, SIGNAL(revisionClicked(QString,bool)), + this, SLOT(revisionSelected(QString,bool)) ); + + plain = new LogPlainView(this); + connect( plain, SIGNAL(revisionClicked(QString,bool)), + this, SLOT(revisionSelected(QString,bool)) ); + + tabWidget = new QTabWidget(splitter); + tabWidget->addTab(tree, i18n("&Tree")); + tabWidget->addTab(listWidget, i18n("&List")); + tabWidget->addTab(plain, i18n("CVS &Output")); + + connect(tabWidget, SIGNAL(currentChanged(QWidget*)), + this, SLOT(tabChanged(QWidget*))); + + QWhatsThis::add(tree, i18n("Choose revision A by clicking with the left " + "mouse button,\nrevision B by clicking with " + "the middle mouse button.")); + + items.setAutoDelete(true); + tags.setAutoDelete(true); + + QWidget *mainWidget = new QWidget(splitter); + QBoxLayout *layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + for (int i = 0; i < 2; ++i) + { + if ( i == 1 ) + { + QFrame *frame = new QFrame(mainWidget); + frame->setFrameStyle(QFrame::HLine | QFrame::Sunken); + layout->addWidget(frame); + } + + QGridLayout *grid = new QGridLayout(layout); + grid->setRowStretch(0, 0); + grid->setRowStretch(1, 0); + grid->setRowStretch(2, 1); + grid->setColStretch(0, 0); + grid->setColStretch(1, 1); + grid->setColStretch(2, 0); + grid->setColStretch(3, 1); + grid->setColStretch(4, 2); + + QString versionident = (i==0)? i18n("Revision A:") : i18n("Revision B:"); + QLabel *versionlabel = new QLabel(versionident, mainWidget); + grid->addWidget(versionlabel, 0, 0); + + revbox[i] = new QLabel(mainWidget); + revbox[i]->setFrameStyle(QFrame::Panel | QFrame::Sunken); + grid->addWidget(revbox[i], 0, 1, Qt::AlignVCenter); + + QLabel *selectlabel = new QLabel(i18n("Select by tag:"), mainWidget); + grid->addWidget(selectlabel, 0, 2); + + tagcombo[i] = new QComboBox(mainWidget); + QFontMetrics fm(tagcombo[i]->fontMetrics()); + tagcombo[i]->setMinimumWidth(fm.width("X")*20); + grid->addWidget(tagcombo[i], 0, 3); + + QLabel *authorlabel = new QLabel(i18n("Author:"), mainWidget); + grid->addWidget(authorlabel, 1, 0); + + authorbox[i] = new QLabel(mainWidget); + authorbox[i]->setFrameStyle(QFrame::Panel | QFrame::Sunken); + grid->addWidget(authorbox[i], 1, 1); + + QLabel *datelabel = new QLabel(i18n("Date:"), mainWidget); + grid->addWidget(datelabel, 1, 2); + + datebox[i] = new QLabel(mainWidget); + datebox[i]->setFrameStyle(QFrame::Panel | QFrame::Sunken); + grid->addWidget(datebox[i], 1, 3); + + QLabel *commentlabel = new QLabel(i18n("Comment/Tags:"), mainWidget); + grid->addWidget(commentlabel, 2, 0); + + commentbox[i] = new QTextEdit(mainWidget); + commentbox[i]->setReadOnly(true); + commentbox[i]->setTextFormat(Qt::PlainText); + fm = commentbox[i]->fontMetrics(); + commentbox[i]->setMinimumHeight(2*fm.lineSpacing()+10); + grid->addMultiCellWidget(commentbox[i], 2, 2, 1, 3); + + tagsbox[i] = new QTextEdit(mainWidget); + tagsbox[i]->setReadOnly(true); + tagsbox[i]->setMinimumHeight(2*fm.lineSpacing()+10); + grid->addWidget(tagsbox[i], 2, 4); + } + + QWhatsThis::add(revbox[0], i18n("This revision is used when you click " + "Annotate.\nIt is also used as the first " + "item of a Diff operation.")); + QWhatsThis::add(revbox[1], i18n("This revision is used as the second " + "item of a Diff operation.")); + + connect( tagcombo[0], SIGNAL(activated(int)), + this, SLOT(tagASelected(int)) ); + connect( tagcombo[1], SIGNAL(activated(int)), + this, SLOT(tagBSelected(int)) ); + + connect( this, SIGNAL(user1Clicked()), + this, SLOT(annotateClicked()) ); + connect( this, SIGNAL(user2Clicked()), + this, SLOT(diffClicked()) ); + connect( this, SIGNAL(user3Clicked()), + this, SLOT(findClicked()) ); + + setButtonGuiItem(Ok, KGuiItem(i18n("to view something", "&View"),"fileopen")); + setButtonGuiItem(Apply, KGuiItem(i18n("Create Patch..."))); + setHelp("browsinglogs"); + + setWFlags(Qt::WDestructiveClose | getWFlags()); + + QSize size = configDialogSize(partConfig, "LogDialog"); + resize(size); + + KConfigGroupSaver cs(&partConfig, "LogDialog"); + tabWidget->setCurrentPage(partConfig.readNumEntry("ShowTab", 0)); + + updateButtons(); +} + + +LogDialog::~LogDialog() +{ + saveDialogSize(partConfig, "LogDialog"); + + KConfigGroupSaver cs(&partConfig, "LogDialog"); + partConfig.writeEntry("ShowTab", tabWidget->currentPageIndex()); +} + + +bool LogDialog::parseCvsLog(CvsService_stub* service, const QString& fileName) +{ + QString rev; + + Cervisia::LogInfo logInfo; + + enum { Begin, Tags, Admin, Revision, + Author, Branches, Comment, Finished } state; + + // remember DCOP reference and file name for diff or annotate + cvsService = service; + filename = fileName; + + setCaption(i18n("CVS Log: %1").arg(filename)); + + DCOPRef job = cvsService->log(filename); + if( !cvsService->ok() ) + return false; + + ProgressDialog dlg(this, "Logging", job, "log", i18n("CVS Log")); + if( !dlg.execute() ) + return false; + + // process cvs log output + state = Begin; + QString line; + while( dlg.getLine(line) ) + { + switch( state ) + { + case Begin: + if( line == "symbolic names:" ) + state = Tags; + break; + case Tags: + if( line[0] == '\t' ) + { + const QStringList strlist(splitLine(line, ':')); + rev = strlist[1].simplifyWhiteSpace(); + const QString tag(strlist[0].simplifyWhiteSpace()); + QString branchpoint; + int pos1, pos2; + if( (pos2 = rev.findRev('.')) > 0 && + (pos1 = rev.findRev('.', pos2-1)) > 0 && + rev.mid(pos1+1, pos2-pos1-1) == "0" ) + { + // For a branch tag 2.10.0.6, we want: + // branchpoint = "2.10" + // rev = "2.10.6" + branchpoint = rev.left(pos1); + rev.remove(pos1+1, pos2-pos1); + } + if( rev != "1.1.1" ) + { + LogDialogTagInfo *taginfo = new LogDialogTagInfo; + taginfo->rev = rev; + taginfo->tag = tag; + taginfo->branchpoint = branchpoint; + tags.append(taginfo); + } + } + else + { + state = Admin; + } + break; + case Admin: + if( line == "----------------------------" ) + { + state = Revision; + } + break; + case Revision: + logInfo.m_revision = rev = line.section(' ', 1, 1); + state = Author; + break; + case Author: + { + QStringList strList = QStringList::split(";", line); + + // convert date into ISO format (YYYY-MM-DDTHH:MM:SS) + int len = strList[0].length(); + QString dateTimeStr = strList[0].right(len-6); // remove 'date: ' + dateTimeStr.replace('/', '-'); + + QString date = dateTimeStr.section(' ', 0, 0); + QString time = dateTimeStr.section(' ', 1, 1); + logInfo.m_dateTime.setTime_t(KRFCDate::parseDateISO8601(date + 'T' + time)); + + logInfo.m_author = strList[1].section(':', 1, 1).stripWhiteSpace(); + + state = Branches; + } + break; + case Branches: + if( !line.startsWith("branches:") ) + { + logInfo.m_comment = line; + state = Comment; + } + break; + case Comment: + if( line == "----------------------------" ) + { + state = Revision; + } + else if( line == "=============================================================================" ) + { + state = Finished; + } + if( state == Comment ) // still in message + logInfo.m_comment += '\n' + line; + else + { + // Create tagcomment + QString branchrev; + int pos1, pos2; + // 1.60.x.y => revision belongs to branch 1.60.0.x + if( (pos2 = rev.findRev('.')) > 0 && + (pos1 = rev.findRev('.', pos2-1)) > 0 ) + branchrev = rev.left(pos2); + + // Build Cervisia::TagInfo for logInfo + QPtrListIterator<LogDialogTagInfo> it(tags); + for( ; it.current(); ++it ) + { + if( rev == it.current()->rev ) + { + // This never matches branch tags... + logInfo.m_tags.push_back(Cervisia::TagInfo(it.current()->tag, + Cervisia::TagInfo::Tag)); + } + if( rev == it.current()->branchpoint ) + { + logInfo.m_tags.push_back(Cervisia::TagInfo(it.current()->tag, + Cervisia::TagInfo::Branch)); + } + if( branchrev == it.current()->rev ) + { + // ... and this never matches ordinary tags :-) + logInfo.m_tags.push_back(Cervisia::TagInfo(it.current()->tag, + Cervisia::TagInfo::OnBranch)); + } + } + + plain->addRevision(logInfo); + tree->addRevision(logInfo); + list->addRevision(logInfo); + + items.append(new Cervisia::LogInfo(logInfo)); + + // reset for next entry + logInfo = Cervisia::LogInfo(); + } + break; + case Finished: + ; + } + } + + tagcombo[0]->insertItem(QString::null); + tagcombo[1]->insertItem(QString::null); + QPtrListIterator<LogDialogTagInfo> it(tags); + for( ; it.current(); ++it ) + { + QString str = it.current()->tag; + if( !it.current()->branchpoint.isEmpty() ) + str += i18n(" (Branchpoint)"); + tagcombo[0]->insertItem(str); + tagcombo[1]->insertItem(str); + } + + plain->scrollToTop(); + + tree->collectConnections(); + tree->recomputeCellSizes(); + + return true; // successful +} + + +void LogDialog::slotOk() +{ + // make sure that the user selected a revision + if( selectionA.isEmpty() && selectionB.isEmpty() ) + { + KMessageBox::information(this, + i18n("Please select revision A or B first."), "Cervisia"); + return; + } + + // retrieve the selected revision + QString revision; + if( !selectionA.isEmpty() ) + revision = selectionA; + else + revision = selectionB; + + // create a temporary file + const QString suffix("-" + revision + "-" + QFileInfo(filename).fileName()); + const QString tempFileName(::tempFileName(suffix)); + + // retrieve the file with the selected revision from cvs + // and save the content into the temporary file + DCOPRef job = cvsService->downloadRevision(filename, revision, tempFileName); + if( !cvsService->ok() ) + return; + + ProgressDialog dlg(this, "View", job, "view", i18n("View File")); + if( dlg.execute() ) + { + // make file read-only + chmod(QFile::encodeName(tempFileName), 0400); + + // open file in preferred editor + KURL url; + url.setPath(tempFileName); + (void) new KRun(url, 0, true, false); + } +} + + +void LogDialog::slotApply() +{ + if( selectionA.isEmpty() ) + { + KMessageBox::information(this, + i18n("Please select revision A or revisions A and B first."), + "Cervisia"); + return; + } + + Cervisia::PatchOptionDialog optionDlg; + if( optionDlg.exec() == KDialogBase::Rejected ) + return; + + QString format = optionDlg.formatOption(); + QString diffOptions = optionDlg.diffOptions(); + + DCOPRef job = cvsService->diff(filename, selectionA, selectionB, diffOptions, + format); + if( !cvsService->ok() ) + return; + + ProgressDialog dlg(this, "Diff", job, "", i18n("CVS Diff")); + if( !dlg.execute() ) + return; + + QString fileName = KFileDialog::getSaveFileName(); + if( fileName.isEmpty() ) + return; + + if( !Cervisia::CheckOverwrite(fileName) ) + return; + + QFile f(fileName); + if( !f.open(IO_WriteOnly) ) + { + KMessageBox::sorry(this, + i18n("Could not open file for writing."), + "Cervisia"); + return; + } + + QTextStream t(&f); + QString line; + while( dlg.getLine(line) ) + t << line << '\n'; + + f.close(); +} + + +void LogDialog::findClicked() +{ + KFindDialog dlg(this); + if( dlg.exec() == KDialogBase::Accepted ) + plain->searchText(dlg.options(), dlg.pattern()); +} + + +void LogDialog::diffClicked() +{ + if (selectionA.isEmpty()) + { + KMessageBox::information(this, + i18n("Please select revision A or revisions A and B first."), + "Cervisia"); + return; + } + + // Non-modal dialog + DiffDialog *l = new DiffDialog(partConfig); + if (l->parseCvsDiff(cvsService, filename, selectionA, selectionB)) + l->show(); + else + delete l; +} + + +void LogDialog::annotateClicked() +{ + AnnotateDialog *l = new AnnotateDialog(partConfig); + AnnotateController ctl(l, cvsService); + ctl.showDialog(filename, selectionA); +} + + +void LogDialog::revisionSelected(QString rev, bool rmb) +{ + QPtrListIterator<Cervisia::LogInfo> it(items); + for (; it.current(); ++it) + if (it.current()->m_revision == rev) + { + if (rmb) + selectionB = rev; + else + selectionA = rev; + + revbox[rmb?1:0]->setText(rev); + authorbox[rmb?1:0]->setText(it.current()->m_author); + datebox[rmb?1:0]->setText(it.current()->dateTimeToString()); + commentbox[rmb?1:0]->setText(it.current()->m_comment); + tagsbox[rmb?1:0]->setText(it.current()->tagsToString()); + + tree->setSelectedPair(selectionA, selectionB); + list->setSelectedPair(selectionA, selectionB); + + updateButtons(); + return; + } + kdDebug(8050) << "Internal error: Revision not found " << rev << "." << endl; +} + + +void LogDialog::tagSelected(LogDialogTagInfo* tag, bool rmb) +{ + if (tag->branchpoint.isEmpty()) + revisionSelected(tag->rev, rmb); + else + revisionSelected(tag->branchpoint, rmb); +} + + +void LogDialog::updateButtons() +{ + // no versions selected? + if( selectionA.isEmpty() && selectionB.isEmpty() ) + { + enableButton(User1, true); // annotate + enableButton(User2, false); // diff + enableButtonOK(false); // view + enableButtonApply(false); // create patch + } + // both versions selected? + else if( !selectionA.isEmpty() && !selectionB.isEmpty() ) + { + enableButton(User1, false); // annotate + enableButton(User2, true); // diff + enableButtonOK(false); // view + enableButtonApply(true); // create patch + } + // only single version selected? + else + { + enableButton(User1, true); // annotate + enableButton(User2, true); // diff + enableButtonOK(true); // view + enableButtonApply(true); // create patch + } +} + + +void LogDialog::tagASelected(int n) +{ + if (n) + tagSelected(tags.at(n-1), false); +} + + +void LogDialog::tagBSelected(int n) +{ + if (n) + tagSelected(tags.at(n-1), true); +} + + +void LogDialog::tabChanged(QWidget* w) +{ + bool isPlainView = (w == plain); + showButton(User3, isPlainView); +} + +#include "logdlg.moc" + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/logdlg.h b/cervisia/logdlg.h new file mode 100644 index 00000000..c5cce4b3 --- /dev/null +++ b/cervisia/logdlg.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef LOGDLG_H +#define LOGDLG_H + +#include <kdialogbase.h> + +#include "loginfo.h" + +#include <qptrlist.h> + + +class LogListView; +class LogTreeView; +class LogPlainView; + +class KConfig; + +class QComboBox; +class QLabel; +class QTabWidget; +class QTextEdit; +class CvsService_stub; + +class LogDialogTagInfo +{ +public: + QString rev; + QString tag; + QString branchpoint; +}; + + +class LogDialog : public KDialogBase +{ + Q_OBJECT + +public: + explicit LogDialog( KConfig& cfg, QWidget *parent=0, const char *name=0 ); + + virtual ~LogDialog(); + + bool parseCvsLog(CvsService_stub* service, const QString& fileName); + +protected slots: + void slotOk(); + void slotApply(); + +private slots: + void findClicked(); + void diffClicked(); + void annotateClicked(); + void revisionSelected(QString rev, bool rmb); + void tagASelected(int n); + void tagBSelected(int n); + void tabChanged(QWidget* w); + +private: + void tagSelected(LogDialogTagInfo* tag, bool rmb); + void updateButtons(); + + QString filename; + QPtrList<Cervisia::LogInfo> items; + QPtrList<LogDialogTagInfo> tags; + QString selectionA; + QString selectionB; + LogTreeView *tree; + LogListView *list; + LogPlainView *plain; + QTabWidget *tabWidget; + QLabel *revbox[2]; + QLabel *authorbox[2]; + QLabel *datebox[2]; + QTextEdit *commentbox[2]; + QTextEdit *tagsbox[2]; + QComboBox *tagcombo[2]; + + CvsService_stub* cvsService; + KConfig& partConfig; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/loginfo.cpp b/cervisia/loginfo.cpp new file mode 100644 index 00000000..49f7efb2 --- /dev/null +++ b/cervisia/loginfo.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "loginfo.h" + +#include <qstylesheet.h> + +#include <kglobal.h> +#include <klocale.h> + + +namespace Cervisia +{ + + +TagInfo::TagInfo(const QString& name, Type type) + : m_name(name), + m_type(type) +{ +} + + +QString TagInfo::toString(bool prefixWithType) const +{ + QString text; + if (prefixWithType) + { + text += typeToString() + QString::fromLatin1(": "); + } + text += m_name; + + return text; +} + + +QString TagInfo::typeToString() const +{ + QString text; + switch (m_type) + { + case Branch: + text = i18n("Branchpoint"); + break; + case OnBranch: + text = i18n("On Branch"); + break; + case Tag: + text = i18n("Tag"); + break; + } + + return text; +} + + +QString LogInfo::createToolTipText(bool showTime) const +{ + QString text(QString::fromLatin1("<nobr><b>")); + text += QStyleSheet::escape(m_revision); + text += QString::fromLatin1("</b> "); + text += QStyleSheet::escape(m_author); + text += QString::fromLatin1(" <b>"); + text += QStyleSheet::escape(dateTimeToString(showTime)); + text += QString::fromLatin1("</b></nobr>"); + + if (!m_comment.isEmpty()) + { + text += QString::fromLatin1("<pre>"); + text += QStyleSheet::escape(m_comment); + text += QString::fromLatin1("</pre>"); + } + + if (!m_tags.isEmpty()) + { + text += QString::fromLatin1("<i>"); + for (TTagInfoSeq::const_iterator it = m_tags.begin(); + it != m_tags.end(); ++it) + { + if (it != m_tags.begin() || m_comment.isEmpty()) + text += QString::fromLatin1("<br>"); + text += QStyleSheet::escape((*it).toString()); + } + text += QString::fromLatin1("</i>"); + } + + return text; +} + + +QString LogInfo::dateTimeToString(bool showTime, bool shortFormat) const +{ + if( showTime ) + return KGlobal::locale()->formatDateTime(m_dateTime, shortFormat); + else + return KGlobal::locale()->formatDate(m_dateTime.date(), shortFormat); +} + + +QString LogInfo::tagsToString(unsigned int types, + unsigned int prefixWithType, + const QString& separator) const +{ + QString text; + for (TTagInfoSeq::const_iterator it = m_tags.begin(); + it != m_tags.end(); ++it) + { + const TagInfo& tagInfo(*it); + + if (tagInfo.m_type & types) + { + if (!text.isEmpty()) + { + text += separator; + } + + text += tagInfo.toString(tagInfo.m_type & prefixWithType); + } + } + + return text; +} + + +} // namespace Cervisia diff --git a/cervisia/loginfo.h b/cervisia/loginfo.h new file mode 100644 index 00000000..e4eb7071 --- /dev/null +++ b/cervisia/loginfo.h @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef CERVISIA_LOGINFO_H +#define CERVISIA_LOGINFO_H + + +#include <qdatetime.h> +#include <qstring.h> +#include <qvaluelist.h> + + +namespace Cervisia +{ + + +/** + * Dumb data struct to store informations of a tag plus some + * convenience methods. The struct is used by the LogInfo struct. + */ +struct TagInfo +{ + /** + * The types of a tag. + */ + enum Type + { + /** + * Branchpoint. + */ + Branch = 1 << 0, + + /** + * This type is for internal use. If the revision is in a branch + * this tag represents the branch. + */ + OnBranch = 1 << 1, + + /** + * Normal tag. + */ + Tag = 1 << 2 + }; + + explicit TagInfo(const QString& name = QString::null, Type type = Tag); + + /** + * @param prefixWithType prefix the string with the type of the tag + * (e.g. Tag: KDE_3_1_3_RELEASE). + * + * @return tag as string. + */ + QString toString(bool prefixWithType = true) const; + + /** + * @return type of tag as string. + */ + QString typeToString() const; + + /** + * The name of the tag. + */ + QString m_name; + + /** + * The type of the tag. + */ + Type m_type; +}; + + +/** + * Dumb data struct to store the results of the log command plus some + * convenience methods. + */ +struct LogInfo +{ + typedef QValueList<TagInfo> TTagInfoSeq; + + /** + * @param showTime show commit time in tooltip. + * + * @return rich text formatted tooltip text. + */ + QString createToolTipText(bool showTime = true) const; + + /** + * Calls KLocale::formatDateTime() to create a formatted string. + * + * @param showTime show commit time in tooltip. + * @param shortFormat using the short date format. + * + * @return The date/time formatted to the user's locale's conventions. + */ + QString dateTimeToString(bool showTime = true, bool shortFormat = true) const; + + enum + { + NoTagType = 0, + AllTagTypes = TagInfo::Branch | TagInfo::OnBranch | TagInfo::Tag + }; + + /** + * Creates a single string from alls tags. + * + * @param types tags that should be taken into account. + * @param prefixWithType tags that should be prefixed with their type + * (see TagInfo::toString()). + * @param separator string to separate the tags. + * + * @return string of joined tags. + */ + QString tagsToString(unsigned int types = AllTagTypes, + unsigned int prefixWithType = AllTagTypes, + const QString& separator = QString(QChar('\n'))) const; + + /** + * The revision of this entry. + */ + QString m_revision; + + /** + * The author who committed. + */ + QString m_author; + + /** + * The commit message. + */ + QString m_comment; + + /** + * The date/time of the commit. + */ + QDateTime m_dateTime; + + /** + * Sequence of tags of this entry. + */ + TTagInfoSeq m_tags; +}; + + +} // namespace Cervisia + + +#endif // CERVISIA_LOGINFO_H diff --git a/cervisia/loglist.cpp b/cervisia/loglist.cpp new file mode 100644 index 00000000..101dd3d7 --- /dev/null +++ b/cervisia/loglist.cpp @@ -0,0 +1,233 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "loglist.h" + +#include <qapplication.h> +#include <qkeycode.h> +#include <klocale.h> + +#include "loginfo.h" +#include "misc.h" +#include "tooltip.h" + + +class LogListViewItem : public KListViewItem +{ +public: + + enum { Revision, Author, Date, Branch, Comment, Tags }; + + LogListViewItem(QListView* list, const Cervisia::LogInfo& logInfo); + + virtual int compare(QListViewItem* i, int col, bool) const; + +private: + static QString truncateLine(const QString &s); + + Cervisia::LogInfo m_logInfo; + friend class LogListView; +}; + + +LogListViewItem::LogListViewItem(QListView* list, const Cervisia::LogInfo& logInfo) + : KListViewItem(list), + m_logInfo(logInfo) +{ + setText(Revision, logInfo.m_revision); + setText(Author, logInfo.m_author); + setText(Date, logInfo.dateTimeToString()); + setText(Comment, truncateLine(logInfo.m_comment)); + + for (Cervisia::LogInfo::TTagInfoSeq::const_iterator it = logInfo.m_tags.begin(); + it != logInfo.m_tags.end(); ++it) + { + const Cervisia::TagInfo& tagInfo(*it); + + if (tagInfo.m_type == Cervisia::TagInfo::OnBranch) + { + setText(Branch, tagInfo.m_name); + } + } + + setText(Tags, logInfo.tagsToString(Cervisia::TagInfo::Tag, + Cervisia::LogInfo::NoTagType, + QString::fromLatin1(", "))); +} + + +QString LogListViewItem::truncateLine(const QString &s) +{ + int pos; + + QString res = s.simplifyWhiteSpace(); + if ( (pos = res.find('\n')) != -1 ) + res = res.left(pos) + "..."; + + return res; +} + + +int LogListViewItem::compare(QListViewItem* i, int col, bool ascending) const +{ + const LogListViewItem* item = static_cast<LogListViewItem*>(i); + + int iResult; + switch (col) + { + case Revision: + iResult = ::compareRevisions(m_logInfo.m_revision, item->m_logInfo.m_revision); + break; + case Date: + iResult = ::compare(m_logInfo.m_dateTime, item->m_logInfo.m_dateTime); + break; + default: + iResult = QListViewItem::compare(i, col, ascending); + } + + return iResult; +} + + +LogListView::LogListView(KConfig& cfg, QWidget *parent, const char *name) + : KListView(parent, name) + , partConfig(cfg) +{ + setAllColumnsShowFocus(true); + setShowToolTips(false); + setShowSortIndicator(true); + setMultiSelection(true); + setSorting(LogListViewItem::Revision, false); + addColumn(i18n("Revision")); + addColumn(i18n("Author")); + addColumn(i18n("Date")); + addColumn(i18n("Branch")); + addColumn(i18n("Comment")); + addColumn(i18n("Tags")); + + Cervisia::ToolTip* toolTip = new Cervisia::ToolTip(viewport()); + + connect(toolTip, SIGNAL(queryToolTip(const QPoint&, QRect&, QString&)), + this, SLOT(slotQueryToolTip(const QPoint&, QRect&, QString&))); + + // without this restoreLayout() can't change the column widths + for (int i = 0; i < columns(); ++i) + setColumnWidthMode(i, Manual); + + restoreLayout(&partConfig, QString::fromLatin1("LogList view")); +} + + +LogListView::~LogListView() +{ + saveLayout(&partConfig, QString::fromLatin1("LogList view")); +} + + +void LogListView::addRevision(const Cervisia::LogInfo& logInfo) +{ + (void) new LogListViewItem(this, logInfo); +} + + +void LogListView::setSelectedPair(const QString &selectionA, const QString &selectionB) +{ + for ( QListViewItem *item = firstChild(); item; + item = item->nextSibling() ) + { + LogListViewItem *i = static_cast<LogListViewItem*>(item); + setSelected(i, (selectionA == i->text(LogListViewItem::Revision) || + selectionB == i->text(LogListViewItem::Revision)) ); + } +} + +void LogListView::contentsMousePressEvent(QMouseEvent *e) +{ + // Retrieve selected item + const LogListViewItem* selItem + = static_cast<LogListViewItem*>(itemAt(contentsToViewport(e->pos()))); + if( !selItem ) + return; + + // Retrieve revision + const QString revision = selItem->text(LogListViewItem::Revision); + + if ( e->button() == LeftButton ) + { + // If the control key was pressed, then we change revision B not A + if( e->state() & ControlButton ) + emit revisionClicked(revision, true); + else + emit revisionClicked(revision, false); + } + else if ( e->button() == MidButton ) + emit revisionClicked(revision, true); +} + + +void LogListView::keyPressEvent(QKeyEvent *e) +{ + switch (e->key()) { + case Key_A: + if (currentItem()) + emit revisionClicked(currentItem()->text(LogListViewItem::Revision), false); + break; + break; + case Key_B: + if (currentItem()) + emit revisionClicked(currentItem()->text(LogListViewItem::Revision), true); + break; + case Key_Backspace: + case Key_Delete: + case Key_Down: + case Key_Up: + case Key_Home: + case Key_End: + case Key_Next: + case Key_Prior: + if (e->state() == 0) + QListView::keyPressEvent(e); + else + QApplication::postEvent(this, new QKeyEvent(QEvent::KeyPress, e->key(), e->ascii(), 0)); + break; + default: + // Ignore Key_Enter, Key_Return + e->ignore(); + } +} + + +void LogListView::slotQueryToolTip(const QPoint& viewportPos, + QRect& viewportRect, + QString& text) +{ + if (const LogListViewItem* item = static_cast<LogListViewItem*>(itemAt(viewportPos))) + { + viewportRect = itemRect(item); + text = item->m_logInfo.createToolTipText(); + } +} + + +#include "loglist.moc" + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/loglist.h b/cervisia/loglist.h new file mode 100644 index 00000000..dc2bca08 --- /dev/null +++ b/cervisia/loglist.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef LOGLIST_H +#define LOGLIST_H + + +#include <klistview.h> + + +class KConfig; + +class TipLabel; +class LogListViewItem; + +namespace Cervisia +{ +struct LogInfo; +} + + +class LogListView : public KListView +{ + Q_OBJECT + +public: + explicit LogListView( KConfig& cfg, QWidget *parent=0, const char *name=0 ); + virtual ~LogListView(); + + void addRevision(const Cervisia::LogInfo& logInfo); + void setSelectedPair(const QString &selectionA, const QString &selectionB); + +signals: + void revisionClicked(QString rev, bool rmb); + +protected: + virtual void contentsMousePressEvent(QMouseEvent *e); + virtual void keyPressEvent(QKeyEvent *e); + +private slots: + + void slotQueryToolTip(const QPoint&, QRect&, QString&); + +private: + + KConfig& partConfig; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/logmessageedit.cpp b/cervisia/logmessageedit.cpp new file mode 100644 index 00000000..33599409 --- /dev/null +++ b/cervisia/logmessageedit.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2004 Jason Kivlighn <mizunoami44@users.sourceforge.net> + * Copyright (c) 2005 Christian Loose <christian.loose@kdemail.net> + * + * based on work by Jason Kivlighn (krecipes/src/widgets/kretextedit.cpp) + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "logmessageedit.h" +using Cervisia::LogMessageEdit; + +#include <qtextstream.h> +#include <kaccel.h> + + +LogMessageEdit::LogMessageEdit(QWidget* parent) + : KTextEdit(parent) + , KCompletionBase() + , m_completing(false) + , m_completionStartPos(0) +{ + // create the completion object + completionObject(); + + // a mouse click stops the completion process + connect( this, SIGNAL(clicked(int, int)), SLOT(stopCompletion()) ); +} + + +void LogMessageEdit::setCompletedText(const QString& match) +{ + int para, index; + getCursorPosition(¶, &index); + + QString paragraphText = text(para); + int length = index - m_completionStartPos; + QString word = match.right(match.length() - length); + + insert(word); + + setSelection(para, index, para, m_completionStartPos + match.length()); + setCursorPosition(para, index); + + m_completing = true; + + // disable spellchecker during completion process. Otherwise we lose the + // text selection. + setCheckSpellingEnabled(false); +} + + +void LogMessageEdit::setCompletedItems(const QStringList&) +{ +} + + +void LogMessageEdit::keyPressEvent(QKeyEvent* event) +{ + bool noModifier = (event->state() == NoButton || + event->state() == ShiftButton || + event->state() == Keypad); + + if( noModifier ) + { + QString keycode = event->text(); + if( !keycode.isEmpty() && keycode.unicode()->isPrint() ) + { + KTextEdit::keyPressEvent(event); + tryCompletion(); + event->accept(); + return; + } + } + + KeyBindingMap keys = getKeyBindings(); + + // handle text completion key + KShortcut shortcut = keys[TextCompletion]; + if( shortcut.isNull() ) + shortcut = KStdAccel::shortcut(KStdAccel::TextCompletion); + + KKey key(event); + + // accept the suggested completion? + if( m_completing && shortcut.contains(key) ) + { + int paraFrom, indexFrom, paraTo, indexTo; + getSelection(¶From, &indexFrom, ¶To, &indexTo); + + removeSelection(); + setCursorPosition(paraTo, indexTo); + + m_completing = false; + setCheckSpellingEnabled(true); + + return; + } + + // handle previous match key + shortcut = keys[PrevCompletionMatch]; + if( shortcut.isNull() ) + shortcut = KStdAccel::shortcut(KStdAccel::PrevCompletion); + + if( shortcut.contains(key) ) + { + rotateMatches(PrevCompletionMatch); + return; + } + + // handle next match key + shortcut = keys[NextCompletionMatch]; + if( shortcut.isNull() ) + shortcut = KStdAccel::shortcut(KStdAccel::NextCompletion); + + if( shortcut.contains(key) ) + { + rotateMatches(NextCompletionMatch); + return; + } + + // any other key (except modifiers) will end the text completion + if( event->key() != Qt::Key_Shift && event->key() != Qt::Key_Control && + event->key() != Qt::Key_Alt && event->key() != Qt::Key_Meta ) + { + m_completing = false; + setCheckSpellingEnabled(true); + } + + KTextEdit::keyPressEvent(event); +} + + +void LogMessageEdit::stopCompletion() +{ + m_completing = false; + setCheckSpellingEnabled(true); +} + + +void LogMessageEdit::tryCompletion() +{ + int para, index; + getCursorPosition(¶, &index); + + QString paragraphText = text(para); + if( paragraphText.at(index).isSpace() ) + { + if( !m_completing ) + m_completionStartPos = paragraphText.findRev(' ', index-1) + 1; + + int length = index - m_completionStartPos; + QString word = paragraphText.mid(m_completionStartPos, length); + + QString match = compObj()->makeCompletion(word); + if( !match.isNull() && match != word ) + { + setCompletedText(match); + } + else + { + m_completing = false; + setCheckSpellingEnabled(true); + } + } +} + + +void LogMessageEdit::rotateMatches(KeyBindingType type) +{ + KCompletion* completionObj = compObj(); + if( completionObj && m_completing && + (type == PrevCompletionMatch || type == NextCompletionMatch) ) + { + QString match = (type == PrevCompletionMatch) ? completionObj->previousMatch() + : completionObj->nextMatch(); + + int para, index; + getCursorPosition(¶, &index); + + QString paragraphText = text(para); + + QString word = paragraphText.mid(m_completionStartPos, index - m_completionStartPos); + + if( match.isNull() || match == word ) + return; + + setCompletedText(match); + } +} + +#include "logmessageedit.moc" diff --git a/cervisia/logmessageedit.h b/cervisia/logmessageedit.h new file mode 100644 index 00000000..05de18fa --- /dev/null +++ b/cervisia/logmessageedit.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2004 Jason Kivlighn <mizunoami44@users.sourceforge.net> + * Copyright (c) 2005 Christian Loose <christian.loose@kdemail.net> + * + * based on work by Jason Kivlighn (krecipes/src/widgets/kretextedit.h) + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef CERVISIA_LOGMESSAGEEDIT_H +#define CERVISIA_LOGMESSAGEEDIT_H + +#include <ktextedit.h> +#include <kcompletion.h> + + +namespace Cervisia +{ + + +class LogMessageEdit : public KTextEdit, public KCompletionBase +{ + Q_OBJECT + +public: + explicit LogMessageEdit(QWidget* parent); + + virtual void setCompletedText(const QString& match); + virtual void setCompletedItems(const QStringList& items); + +protected: + void keyPressEvent(QKeyEvent* event); + +private slots: + void stopCompletion(); + +private: + void tryCompletion(); + void rotateMatches(KeyBindingType type); + + bool m_completing; + int m_completionStartPos; +}; + + +} + + +#endif diff --git a/cervisia/logplainview.cpp b/cervisia/logplainview.cpp new file mode 100644 index 00000000..ca28f8b0 --- /dev/null +++ b/cervisia/logplainview.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "logplainview.h" + +#include <qregexp.h> +#include <qstringlist.h> +#include <qstylesheet.h> +#include <kfind.h> +#include <kfinddialog.h> +#include <klocale.h> + +#include "loginfo.h" + +using namespace Cervisia; + + +LogPlainView::LogPlainView(QWidget* parent, const char* name) + : KTextBrowser(parent, name) + , m_find(0) + , m_findPos(0) +{ + setNotifyClick(false); +} + + +LogPlainView::~LogPlainView() +{ + delete m_find; m_find = 0; +} + + +void LogPlainView::addRevision(const LogInfo& logInfo) +{ + setTextFormat(QStyleSheet::RichText); + + // assemble revision information lines + QString logEntry; + + logEntry += "<b>" + i18n("revision %1").arg(QStyleSheet::escape(logInfo.m_revision)) + + "</b>"; + logEntry += " [<a href=\"revA#" + QStyleSheet::escape(logInfo.m_revision) + "\">" + + i18n("Select for revision A") + + "</a>]"; + logEntry += " [<a href=\"revB#" + QStyleSheet::escape(logInfo.m_revision) + "\">" + + i18n("Select for revision B") + + "</a>]<br>"; + logEntry += "<i>" + + i18n("date: %1; author: %2").arg(QStyleSheet::escape(logInfo.dateTimeToString())) + .arg(QStyleSheet::escape(logInfo.m_author)) + + "</i>"; + + append(logEntry); + + setTextFormat(QStyleSheet::PlainText); + + const QChar newline('\n'); + + // split comment in separate lines + QStringList lines = QStringList::split(newline, logInfo.m_comment, true); + + append(newline); + QStringList::Iterator it = lines.begin(); + QStringList::Iterator end = lines.end(); + for( ; it != end; ++it ) + { + append((*it).isEmpty() ? QString(newline) : *it); + } + append(newline); + + setTextFormat(QStyleSheet::RichText); + + for( LogInfo::TTagInfoSeq::const_iterator it = logInfo.m_tags.begin(); + it != logInfo.m_tags.end(); ++it ) + { + append("<i>" + QStyleSheet::escape((*it).toString()) + "</i>"); + } + + // add an empty line when we had tags or branches + if( !logInfo.m_tags.empty() ) + { + setTextFormat(QStyleSheet::PlainText); + append(newline); + } + + // add horizontal line + setTextFormat(QStyleSheet::RichText); + append("<hr>"); +} + + +void LogPlainView::searchText(int options, const QString& pattern) +{ + m_find = new KFind(pattern, options, this); + + connect(m_find, SIGNAL(highlight(const QString&, int, int)), + this, SLOT(searchHighlight(const QString&, int, int))); + connect(m_find, SIGNAL(findNext()), + this, SLOT(findNext())); + + m_findPos = 0; + if( options & KFindDialog::FromCursor ) + { + const QPoint pos(contentsX(), contentsY()); + m_findPos = paragraphAt(pos); + } + + findNext(); +} + + +void LogPlainView::scrollToTop() +{ + setContentsPos(0, 0); +} + + +void LogPlainView::findNext() +{ + static const QRegExp breakLineTag("<br[^>]*>"); + static const QRegExp htmlTags("<[^>]*>"); + + KFind::Result res = KFind::NoMatch; + + while( res == KFind::NoMatch && m_findPos < paragraphs() && m_findPos >= 0 ) + { + if( m_find->needData() ) + { + QString richText = text(m_findPos); + + // replace <br/> with '\n' + richText.replace(breakLineTag, "\n"); + + // remove html tags from text + richText.replace(htmlTags, ""); + + m_find->setData(richText); + } + + res = m_find->find(); + + if( res == KFind::NoMatch ) + { + if( m_find->options() & KFindDialog::FindBackwards ) + --m_findPos; + else + ++m_findPos; + } + } + + // reached the end? + if( res == KFind::NoMatch ) + { + if( m_find->shouldRestart() ) + { + m_findPos = 0; + findNext(); + } + else + { + delete m_find; + m_find = 0; + } + } +} + + +void LogPlainView::searchHighlight(const QString& text, int index, int length) +{ + Q_UNUSED(text); + setSelection(m_findPos, index, m_findPos, index + length); +} + + +void LogPlainView::setSource(const QString& name) +{ + if( name.isEmpty() ) + return; + + bool selectedRevisionB = name.startsWith("revB#"); + if( selectedRevisionB || name.startsWith("revA#") ) + { + emit revisionClicked(name.mid(5), selectedRevisionB); + } +} + +#include "logplainview.moc" diff --git a/cervisia/logplainview.h b/cervisia/logplainview.h new file mode 100644 index 00000000..f5da46a5 --- /dev/null +++ b/cervisia/logplainview.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef LOGPLAINVIEW_H +#define LOGPLAINVIEW_H + +#include <ktextbrowser.h> + +class KConfig; +class KFind; + +namespace Cervisia +{ +struct LogInfo; +} + + +class LogPlainView : public KTextBrowser +{ + Q_OBJECT + +public: + explicit LogPlainView(QWidget* parent = 0, const char* name = 0); + ~LogPlainView(); + + void addRevision(const Cervisia::LogInfo& logInfo); + + void searchText(int options, const QString& pattern); + +signals: + void revisionClicked(QString rev, bool rmb); + +public slots: + void scrollToTop(); + void findNext(); + void searchHighlight(const QString& text, int index, int length); + +protected: + virtual void setSource(const QString& name); + +private: + KFind* m_find; + int m_findPos; +}; + +#endif diff --git a/cervisia/logtree.cpp b/cervisia/logtree.cpp new file mode 100644 index 00000000..1b5258ad --- /dev/null +++ b/cervisia/logtree.cpp @@ -0,0 +1,501 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2003-2004 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "logtree.h" + +#include <qpainter.h> +#include <kdebug.h> +#include <kglobal.h> +#include <kglobalsettings.h> + +#include "loginfo.h" +#include "tooltip.h" + + +const int LogTreeView::BORDER = 8; +const int LogTreeView::INSPACE = 3; + +namespace +{ + bool static_initialized = false; + int static_width; + int static_height; +} + +class LogTreeItem +{ +public: + Cervisia::LogInfo m_logInfo; + QString branchpoint; + bool firstonbranch; + int row; + int col; + bool selected; +}; + + +class LogTreeConnection +{ +public: + LogTreeItem *start; + LogTreeItem *end; +}; + + +LogTreeView::LogTreeView(QWidget *parent, const char *name) + : QTable(parent, name) +{ + if (!static_initialized) + { + static_initialized = true; + QFontMetrics fm( fontMetrics() ); + static_width = fm.width("1234567890") + 2*BORDER + 2*INSPACE; + static_height = 2*fm.height() + 2*BORDER + 3*INSPACE; + } + + setNumCols(0); + setNumRows(0); + setReadOnly(true); + setFocusStyle(QTable::FollowStyle); + setSelectionMode(QTable::NoSelection); + setShowGrid(false); + horizontalHeader()->hide(); + setTopMargin(0); + verticalHeader()->hide(); + setLeftMargin(0); + setFrameStyle( QFrame::WinPanel | QFrame::Sunken ); + setBackgroundMode(PaletteBase); + setFocusPolicy(NoFocus); + + currentRow = -1; + currentCol = -1; + + items.setAutoDelete(true); + connections.setAutoDelete(true); + + Cervisia::ToolTip* toolTip = new Cervisia::ToolTip(viewport()); + + connect(toolTip, SIGNAL(queryToolTip(const QPoint&, QRect&, QString&)), + this, SLOT(slotQueryToolTip(const QPoint&, QRect&, QString&))); +} + + +void LogTreeView::addRevision(const Cervisia::LogInfo& logInfo) +{ + QString branchpoint, branchrev; + + const QString rev(logInfo.m_revision); + + // find branch + int pos1, pos2; + if ((pos2 = rev.findRev('.')) > 0 && + (pos1 = rev.findRev('.', pos2-1)) > 0) + { + // e. g. for rev = 1.1.2.3 we have + // branchrev = 1.1.2, branchpoint = 1.1 + branchrev = rev.left(pos2); + branchpoint = rev.left(pos1); + } + + if (branchrev.isEmpty()) + { + // Most probably we are on the trunk + setNumRows(numRows()+1); + setNumCols(1); + LogTreeItem *item = new LogTreeItem; + item->m_logInfo = logInfo; + item->branchpoint = branchpoint; + item->firstonbranch = false; + item->row = numRows()-1; + item->col = 0; + item->selected = false; + items.append(item); + return; + } + + // look whether we have revisions on this branch + // shift them up + int row=-1, col=-1; + QPtrListIterator<LogTreeItem> it(items); + for (; it.current(); ++it) + { + if (branchrev == (it.current()->m_logInfo.m_revision).left(branchrev.length())) + { + it.current()->firstonbranch = false; + row = it.current()->row; + col = it.current()->col; + it.current()->row--; + // Are we at the top of the widget? + if (row == 0) + { + QPtrListIterator<LogTreeItem> it2(items); + for (; it2.current(); ++it2) + it2.current()->row++; + setNumRows(numRows()+1); + row = 1; + } + } + } + + if (row == -1) + { + // Ok, so we must open a new branch + // Let's find the branch point + QPtrListIterator<LogTreeItem> it3(items); + for (it3.toLast(); it3.current(); --it3) + { + if (branchpoint == it3.current()->m_logInfo.m_revision) + { + // Move existing branches to the right + QPtrListIterator<LogTreeItem> it4(items); + for (; it4.current(); ++it4) + if (it4.current()->col > it3.current()->col) + { + it4.current()->col++; + } + setNumCols(numCols()+1); + row = it3.current()->row-1; + col = it3.current()->col+1; + if (row == -1) + { + QPtrListIterator<LogTreeItem> it5(items); + for (; it5.current(); ++it5) + it5.current()->row++; + setNumRows(numRows()+1); + row = 0; + } + break; + } + } + } + + LogTreeItem *item = new LogTreeItem; + item->m_logInfo = logInfo; + item->branchpoint = branchpoint; + item->firstonbranch = true; + item->row = row; + item->col = col; + item->selected = false; + items.append(item); + +#if 0 + cout << "Dump: " << endl; + cout << "Rows: " << numRows() << "Cols: " << numCols() << endl; + QPtrListIterator<LogTreeItem> it5(items); + for (; it5.current(); ++it5) + { + cout << "Rev: "<< it5.current()->rev << endl; + cout << "row: "<< it5.current()->row << ", col: " << it5.current()->col << endl; + cout << "fob: "<< it5.current()->firstonbranch << endl; + } + cout << "End Dump" << endl; +#endif + +} + + +void LogTreeView::collectConnections() +{ + QPtrListIterator<LogTreeItem> it(items); + for (; it.current(); ++it) + { + QString rev = it.current()->m_logInfo.m_revision; + + QPtrListIterator<LogTreeItem> it2(items); + for (it2=it,++it2; it2.current(); ++it2) + if (it2.current()->branchpoint == rev && + it2.current()->firstonbranch) + { + LogTreeConnection *conn = new LogTreeConnection; + conn->start = it.current(); + conn->end = it2.current(); + connections.append(conn); + } + } +} + + +void LogTreeView::setSelectedPair(QString selectionA, QString selectionB) +{ + QPtrListIterator<LogTreeItem> it(items); + for(; it.current(); ++it) + { + bool oldstate = it.current()->selected; + bool newstate = ( selectionA == it.current()->m_logInfo.m_revision || + selectionB == it.current()->m_logInfo.m_revision ); + if (oldstate != newstate) + { + it.current()->selected = newstate; + repaint(false); + } + } +} + + +QSize LogTreeView::sizeHint() const +{ + return QSize(2 * static_width, 3 * static_height); +} + + +QString LogTreeView::text(int row, int col) const +{ + LogTreeItem* item = 0; + + QPtrListIterator<LogTreeItem> it(items); + for( ; it.current(); ++it ) + { + if( it.current()->col == col && it.current()->row == row ) + { + item = it.current(); + break; + } + } + + QString text; + + if( item && !item->m_logInfo.m_author.isNull() ) + text = item->m_logInfo.createToolTipText(); + + return text; +} + + +void LogTreeView::paintCell(QPainter *p, int row, int col, const QRect& cr, + bool selected, const QColorGroup& cg) +{ + Q_UNUSED(selected) + Q_UNUSED(cr) + bool followed, branched; + LogTreeItem *item; + + branched = false; + followed = false; + item = 0; + + QPtrListIterator<LogTreeItem> it(items); + for(; it.current(); ++it) + { + int itcol = it.current()->col; + int itrow = it.current()->row; + if (itrow == row-1 && itcol == col) + followed = true; + if (itrow == row && itcol == col) + item = it.current(); + } + QPtrListIterator<LogTreeConnection> it2(connections); + for (; it2.current(); ++it2) + { + int itcol1 = it2.current()->start->col; + int itcol2 = it2.current()->end->col; + int itrow = it2.current()->start->row; + if (itrow == row && itcol1 <= col && itcol2 > col) + branched = true; + } + + p->fillRect(0, 0, columnWidth(col), rowHeight(row), + cg.base()); + p->setPen(cg.text()); + if (item) + paintRevisionCell(p, row, col, item->m_logInfo, + followed, branched, item->selected); + else if (followed || branched) + paintConnector(p, row, col, followed, branched); +} + + +void LogTreeView::paintConnector(QPainter *p, + int row, int col, bool followed, bool branched) +{ + const int midx = columnWidth(col) / 2; + const int midy = rowHeight(row) / 2; + + p->drawLine(0, midy, branched ? columnWidth(col) : midx, midy); + if (followed) + p->drawLine(midx, midy, midx, 0); +} + + +QSize LogTreeView::computeSize(const Cervisia::LogInfo& logInfo, + int* authorHeight, + int* tagsHeight) const +{ + const QFontMetrics fm(fontMetrics()); + + const QString tags(logInfo.tagsToString(Cervisia::TagInfo::Branch | Cervisia::TagInfo::Tag, + Cervisia::TagInfo::Branch)); + + const QSize r1 = fm.size(AlignCenter, logInfo.m_revision); + const QSize r3 = fm.size(AlignCenter, logInfo.m_author); + + if (authorHeight) + *authorHeight = r3.height(); + + int infoWidth = kMax(static_width - 2 * BORDER, kMax(r1.width(), r3.width())); + int infoHeight = r1.height() + r3.height() + 3 * INSPACE; + + if (!tags.isEmpty()) + { + const QSize r2 = fm.size(AlignCenter, tags); + infoWidth = kMax(infoWidth, r2.width()); + infoHeight += r2.height() + INSPACE; + if (tagsHeight) + *tagsHeight = r2.height(); + } + else + { + if (tagsHeight) + *tagsHeight = 0; + } + infoWidth += 2 * INSPACE; + + return QSize(infoWidth, infoHeight); +} + + +void LogTreeView::paintRevisionCell(QPainter *p, + int row, int col, + const Cervisia::LogInfo& logInfo, + bool followed, bool branched, bool selected) +{ + int authorHeight; + int tagsHeight; + const QSize infoSize(computeSize(logInfo, &authorHeight, &tagsHeight)); + const QSize cellSize(columnWidth(col), rowHeight(row)); + + const int midx(cellSize.width() / 2); + const int midy(cellSize.height() / 2); + + QRect rect(QPoint((cellSize.width() - infoSize.width()) / 2, + (cellSize.height() - infoSize.height()) / 2), + infoSize); + + // Connectors + if (followed) + p->drawLine(midx, 0, midx, rect.y()); // to the top + + if (branched) + p->drawLine(rect.x() + infoSize.width(), midy, cellSize.width(), midy); // to the right + + p->drawLine(midx, rect.y() + infoSize.height(), midx, cellSize.height()); // to the bottom + + // The box itself + if (selected) + { + p->fillRect(rect, KGlobalSettings::highlightColor()); + p->setPen(KGlobalSettings::highlightedTextColor()); + } + else + { + p->drawRoundRect(rect, 10, 10); + } + + rect.setY(rect.y() + INSPACE); + + p->drawText(rect, AlignHCenter, logInfo.m_author); + rect.setY(rect.y() + authorHeight + INSPACE); + + const QString tags(logInfo.tagsToString(Cervisia::TagInfo::Branch | Cervisia::TagInfo::Tag, + Cervisia::TagInfo::Branch)); + if (!tags.isEmpty()) + { + const QFont font(p->font()); + QFont underline(font); + underline.setUnderline(true); + + p->setFont(underline); + p->drawText(rect, AlignHCenter, tags); + p->setFont(font); + + rect.setY(rect.y() + tagsHeight + INSPACE); + } + + p->drawText(rect, AlignHCenter, logInfo.m_revision); +} + + +void LogTreeView::contentsMousePressEvent(QMouseEvent *e) +{ + if ( e->button() == MidButton || + e->button() == LeftButton) + { + int row = rowAt( e->pos().y() ); + int col = columnAt( e->pos().x() ); + + QPtrListIterator<LogTreeItem> it(items); + for(; it.current(); ++it) + if (it.current()->row == row + && it.current()->col == col) + { + // Change selection for revision B if the middle mouse button or + // the left mouse button with the control key was pressed + bool changeRevB = (e->button() == MidButton) || + (e->button() == LeftButton && + e->state() & ControlButton); + + emit revisionClicked(it.current()->m_logInfo.m_revision, changeRevB); + break; + } + } + + viewport()->update(); +} + + +void LogTreeView::recomputeCellSizes () +{ + // Compute maximum for each column and row + for (QPtrListIterator<LogTreeItem> it(items); it.current(); ++it) + { + const LogTreeItem *item = it.current(); + + const QSize cellSize(computeSize(item->m_logInfo) + QSize(2 * BORDER, 2 * BORDER)); + + setColumnWidth(item->col, kMax(columnWidth(item->col), cellSize.width())); + setRowHeight(item->row, kMax(rowHeight(item->row), cellSize.height())); + } + + viewport()->update(); +} + + +void LogTreeView::slotQueryToolTip(const QPoint& viewportPos, + QRect& viewportRect, + QString& tipText) +{ + const QPoint contentsPos(viewportToContents(viewportPos)); + const int column(columnAt(contentsPos.x())); + const int row(rowAt(contentsPos.y())); + + tipText = text(row, column); + if (tipText.isEmpty()) + return; + + viewportRect = cellGeometry(row, column); + viewportRect.moveTopLeft(contentsToViewport(viewportRect.topLeft())); +} + + +#include "logtree.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/logtree.h b/cervisia/logtree.h new file mode 100644 index 00000000..9ab505bc --- /dev/null +++ b/cervisia/logtree.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2004 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef LOGTREE_H +#define LOGTREE_H + + +#include <qptrlist.h> + +#include <qtable.h> + + +class LogTreeItem; +class LogTreeConnection; + +namespace Cervisia +{ +struct LogInfo; +} + + +typedef QPtrList<LogTreeItem> LogTreeItemList; +typedef QPtrList<LogTreeConnection> LogTreeConnectionList; + + +class LogTreeView : public QTable +{ + Q_OBJECT + +public: + explicit LogTreeView( QWidget *parent=0, const char *name=0 ); + + void addRevision(const Cervisia::LogInfo& logInfo); + void setSelectedPair(QString selectionA, QString selectionB); + void collectConnections(); + void recomputeCellSizes(); + virtual void paintCell(QPainter *p, int row, int col, const QRect& cr, + bool selected, const QColorGroup& cg); + + virtual QSize sizeHint() const; + + virtual QString text(int row, int col) const; + +signals: + void revisionClicked(QString rev, bool rmb); + +protected: + virtual void contentsMousePressEvent(QMouseEvent *e); + +private slots: + + void slotQueryToolTip(const QPoint&, QRect&, QString&); + +private: + QSize computeSize(const Cervisia::LogInfo&, int* = 0, int* = 0) const; + void paintRevisionCell(QPainter *p, int row, int col, const Cervisia::LogInfo& logInfo, + bool followed, bool branched, bool selected); + void paintConnector(QPainter *p, int row, int col, bool followed, bool branched); + + LogTreeItemList items; + LogTreeConnectionList connections; + int currentRow, currentCol; + + static const int BORDER; + static const int INSPACE; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/main.cpp b/cervisia/main.cpp new file mode 100644 index 00000000..e8e6a505 --- /dev/null +++ b/cervisia/main.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2003-2004 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <iostream> + +#include <qfileinfo.h> +#include <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <kconfig.h> +#include <klocale.h> +#include <kurl.h> + +#include "misc.h" +#include "cervisiashell.h" +#include "cvsservice_stub.h" +#include "annotatedlg.h" +#include "annotatectl.h" +#include "logdlg.h" +#include "resolvedlg.h" +#include "version.h" + + +static CvsService_stub* StartDCOPService(const QString& directory) +{ + // start the cvs DCOP service + QString error; + QCString appId; + if( KApplication::startServiceByDesktopName("cvsservice", QStringList(), + &error, &appId) ) + { + std::cerr << "Starting cvsservice failed with message: " + << error.latin1() << std::endl; + exit(1); + } + + DCOPRef repository(appId, "CvsRepository"); + + repository.call("setWorkingCopy(QString)", directory); + + // create a reference to the service + return new CvsService_stub(appId, "CvsService"); +} + + +static int ShowResolveDialog(const QString& fileName) +{ + KConfig* config = new KConfig("cervisiapartrc"); + + ResolveDialog* dlg = new ResolveDialog(*config); + kapp->setMainWidget(dlg); + if( dlg->parseFile(fileName) ) + dlg->show(); + else + delete dlg; + + int result = kapp->exec(); + + delete config; + + return result; +} + + +static int ShowLogDialog(const QString& fileName) +{ + KConfig* config = new KConfig("cervisiapartrc"); + LogDialog* dlg = new LogDialog(*config); + kapp->setMainWidget(dlg); + + // get directory for file + const QFileInfo fi(fileName); + QString directory = fi.dirPath(true); + + // start the cvs DCOP service + CvsService_stub* cvsService = StartDCOPService(directory); + + if( dlg->parseCvsLog(cvsService, fi.fileName()) ) + dlg->show(); + else + delete dlg; + + int result = kapp->exec(); + + // stop the cvs DCOP service + cvsService->quit(); + delete cvsService; + + delete config; + + return result; +} + + +static int ShowAnnotateDialog(const QString& fileName) +{ + KConfig* config = new KConfig("cervisiapartrc"); + AnnotateDialog* dlg = new AnnotateDialog(*config); + kapp->setMainWidget(dlg); + + // get directory for file + const QFileInfo fi(fileName); + QString directory = fi.dirPath(true); + + // start the cvs DCOP service + CvsService_stub* cvsService = StartDCOPService(directory); + + AnnotateController ctl(dlg, cvsService); + ctl.showDialog(fi.fileName()); + + int result = kapp->exec(); + + // stop the cvs DCOP service + cvsService->quit(); + delete cvsService; + + delete config; + + return result; +} + + +extern "C" KDE_EXPORT int kdemain(int argc, char **argv) +{ + static KCmdLineOptions options[] = { + { "+[directory]", I18N_NOOP("The sandbox to be loaded"), 0 }, + { "resolve <file>", I18N_NOOP("Show resolve dialog for the given file"), 0 }, + { "log <file>", I18N_NOOP("Show log dialog for the given file"), 0 }, + { "annotate <file>", I18N_NOOP("Show annotation dialog for the given file"), 0 }, + KCmdLineLastOption + }; + KAboutData about("cervisia", I18N_NOOP("Cervisia"), CERVISIA_VERSION, + I18N_NOOP("A CVS frontend"), KAboutData::License_GPL, + I18N_NOOP("Copyright (c) 1999-2002 Bernd Gehrmann\n" + "Copyright (c) 2002-2007 the Cervisia authors"), 0, + "http://www.kde.org/apps/cervisia"); + + about.addAuthor("Bernd Gehrmann", I18N_NOOP("Original author and former " + "maintainer"), "bernd@mail.berlios.de", 0); + about.addAuthor("Christian Loose", I18N_NOOP("Maintainer"), + "christian.loose@kdemail.net", 0); + about.addAuthor("Andr\303\251 W\303\266bbeking", I18N_NOOP("Developer"), + "woebbeking@kde.org", 0); + about.addAuthor("Carlos Woelz", I18N_NOOP("Documentation"), + "carloswoelz@imap-mail.com", 0); + + about.addCredit("Richard Moore", I18N_NOOP("Conversion to KPart"), + "rich@kde.org", 0); + + KCmdLineArgs::init(argc, argv, &about); + KCmdLineArgs::addCmdLineOptions(options); + + KApplication app; + + QString resolvefile = KCmdLineArgs::parsedArgs()->getOption("resolve"); + if (!resolvefile.isEmpty()) + return ShowResolveDialog(resolvefile); + + // is command line option 'show log dialog' specified? + QString logFile = KCmdLineArgs::parsedArgs()->getOption("log"); + if( !logFile.isEmpty() ) + return ShowLogDialog(logFile); + + // is command line option 'show annotation dialog' specified? + QString annotateFile = KCmdLineArgs::parsedArgs()->getOption("annotate"); + if( !annotateFile.isEmpty() ) + return ShowAnnotateDialog(annotateFile); + + if ( app.isRestored() ) { + RESTORE(CervisiaShell); + } else { + CervisiaShell* shell = new CervisiaShell(); + + const KCmdLineArgs* args = KCmdLineArgs::parsedArgs(); + if( args->count() ) + { + KURL directory = args->url(0); + shell->openURL(directory); + } + else + shell->openURL(); + + shell->setIcon(app.icon()); + app.setMainWidget(shell); + shell->show(); + } + + int res = app.exec(); + cleanupTempFiles(); + return res; +} + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/mergedlg.cpp b/cervisia/mergedlg.cpp new file mode 100644 index 00000000..92d337d5 --- /dev/null +++ b/cervisia/mergedlg.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "mergedlg.h" + +#include <qbuttongroup.h> +#include <qcombobox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qradiobutton.h> +#include <qstyle.h> +#include <klocale.h> + +#include "misc.h" +#include "cvsservice_stub.h" + + +MergeDialog::MergeDialog(CvsService_stub* service, + QWidget *parent, const char *name) + : KDialogBase(parent, name, true, i18n("CVS Merge"), + Ok | Cancel, Ok, true), + cvsService(service) +{ + int const iComboBoxMinWidth(30 * fontMetrics().width('0')); + int const iWidgetIndent(style().pixelMetric(QStyle::PM_ExclusiveIndicatorWidth, 0) + 6); + + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout *layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + bybranch_button = new QRadioButton(i18n("Merge from &branch:"), mainWidget); + bybranch_button->setChecked(true); + layout->addWidget(bybranch_button); + + branch_combo = new QComboBox(true, mainWidget); + branch_combo->setMinimumWidth(iComboBoxMinWidth); + + branch_button = new QPushButton(i18n("Fetch &List"), mainWidget); + connect( branch_button, SIGNAL(clicked()), + this, SLOT(branchButtonClicked()) ); + + QBoxLayout *branchedit_layout = new QHBoxLayout(layout); + branchedit_layout->addSpacing(iWidgetIndent); + branchedit_layout->addWidget(branch_combo, 2); + branchedit_layout->addWidget(branch_button, 0); + + bytags_button = new QRadioButton(i18n("Merge &modifications:"), mainWidget); + layout->addWidget(bytags_button); + + QLabel *tag1_label = new QLabel(i18n("between tag: "), mainWidget); + tag1_combo = new QComboBox(true, mainWidget); + tag1_combo->setMinimumWidth(iComboBoxMinWidth); + + QLabel *tag2_label = new QLabel(i18n("and tag: "), mainWidget); + tag2_combo = new QComboBox(true, mainWidget); + tag2_combo->setMinimumWidth(iComboBoxMinWidth); + + tag_button = new QPushButton(i18n("Fetch L&ist"), mainWidget); + connect( tag_button, SIGNAL(clicked()), + this, SLOT(tagButtonClicked()) ); + + QGridLayout *tagsedit_layout = new QGridLayout(layout); + tagsedit_layout->addColSpacing(0, iWidgetIndent); + tagsedit_layout->setColStretch(0, 0); + tagsedit_layout->setColStretch(1, 1); + tagsedit_layout->setColStretch(2, 2); + tagsedit_layout->setColStretch(3, 0); + tagsedit_layout->addWidget(tag1_label, 0, 1); + tagsedit_layout->addWidget(tag1_combo, 0, 2); + tagsedit_layout->addWidget(tag2_label, 1, 1); + tagsedit_layout->addWidget(tag2_combo, 1, 2); + tagsedit_layout->addMultiCellWidget(tag_button, 0, 1, 3, 3); + + QButtonGroup* group = new QButtonGroup(mainWidget); + group->hide(); + group->insert(bybranch_button); + group->insert(bytags_button); + connect( group, SIGNAL(clicked(int)), + this, SLOT(toggled()) ); + + // dis-/enable the widgets + toggled(); +} + + +bool MergeDialog::byBranch() const +{ + return bybranch_button->isChecked(); +} + + +QString MergeDialog::branch() const +{ + return branch_combo->currentText(); +} + + +QString MergeDialog::tag1() const +{ + return tag1_combo->currentText(); +} + + +QString MergeDialog::tag2() const +{ + return tag2_combo->currentText(); +} + + +void MergeDialog::tagButtonClicked() +{ + QStringList const listTags(::fetchTags(cvsService, this)); + tag1_combo->clear(); + tag1_combo->insertStringList(listTags); + tag2_combo->clear(); + tag2_combo->insertStringList(listTags); +} + + +void MergeDialog::branchButtonClicked() +{ + branch_combo->clear(); + branch_combo->insertStringList(::fetchBranches(cvsService, this)); +} + + +void MergeDialog::toggled() +{ + bool bybranch = bybranch_button->isChecked(); + branch_combo->setEnabled(bybranch); + branch_button->setEnabled(bybranch); + tag1_combo->setEnabled(!bybranch); + tag2_combo->setEnabled(!bybranch); + tag_button->setEnabled(!bybranch); + if (bybranch) + branch_combo->setFocus(); + else + tag1_combo->setFocus(); +} + +#include "mergedlg.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/mergedlg.h b/cervisia/mergedlg.h new file mode 100644 index 00000000..bd4464c1 --- /dev/null +++ b/cervisia/mergedlg.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef MERGEDLG_H +#define MERGEDLG_H + + +#include <kdialogbase.h> + + +class QComboBox; +class QPushButton; +class QRadioButton; +class CvsService_stub; + + +class MergeDialog : public KDialogBase +{ + Q_OBJECT + +public: + MergeDialog( CvsService_stub* service, + QWidget *parent=0, const char *name=0 ); + + bool byBranch() const; + QString branch() const; + QString tag1() const; + QString tag2() const; + +private slots: + void toggled(); + void tagButtonClicked(); + void branchButtonClicked(); + +private: + CvsService_stub* cvsService; + + QRadioButton *bybranch_button, *bytags_button; + QComboBox *branch_combo, *tag1_combo, *tag2_combo; + QPushButton *tag_button, *branch_button; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/misc.cpp b/cervisia/misc.cpp new file mode 100644 index 00000000..e1cfe208 --- /dev/null +++ b/cervisia/misc.cpp @@ -0,0 +1,350 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "misc.h" + +#include <ctype.h> +#include <pwd.h> +#include <sys/types.h> +#include <unistd.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <qregexp.h> +#include <qstringlist.h> +#include <kconfig.h> +#include <kemailsettings.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kprocess.h> +#include <ktempfile.h> +#include <kuser.h> +#include <kdebug.h> + +#include "cvsservice_stub.h" +#include "progressdlg.h" + +// These regular expression parts aren't useful to check the validity of the +// CVSROOT specification. They are just used to extract the different parts of it. +static const QString userNameRegExp("([a-z0-9_][a-z0-9_-.]*)?"); +static const QString passwordRegExp("(:[^@]+)?"); +static const QString hostNameRegExp("([^:/@]+)"); +static const QString portRegExp("(:(\\d*))?"); +static const QString pathRegExp("(/.*)"); + + +static int FindWhiteSpace(const QString& str, int index) +{ + const int length = str.length(); + + if( index < 0 ) + index += length; + + if( index < 0 || index >= length ) + return -1; + + const QChar* const startPos = str.unicode(); + const QChar* const endPos = startPos + length; + + const QChar* pos = startPos + index; + while( pos < endPos && !pos->isSpace() ) + ++pos; + + const int foundIndex = pos - startPos; + return (foundIndex < length ? foundIndex : -1); +} + + +static const QStringList FetchBranchesAndTags(const QString& searchedType, + CvsService_stub* cvsService, + QWidget* parent) +{ + QStringList branchOrTagList; + + DCOPRef job = cvsService->status(QStringList(), true, true); + if( !cvsService->ok() ) + return branchOrTagList; + + ProgressDialog dlg(parent, "Status", job, QString::null, i18n("CVS Status")); + + if( dlg.execute() ) + { + QString line; + while( dlg.getLine(line) ) + { + int wsPos, bracketPos, colonPos; + + if( line.isEmpty() || line[0] != '\t' ) + continue; + if( (wsPos = FindWhiteSpace(line, 2)) < 0 ) + continue; + if( (bracketPos = line.find('(', wsPos + 1)) < 0 ) + continue; + if( (colonPos = line.find(':', bracketPos + 1)) < 0 ) + continue; + + const QString tag = line.mid(1, wsPos - 1); + const QString type = line.mid(bracketPos + 1, colonPos - bracketPos - 1); + if( type == searchedType && !branchOrTagList.contains(tag) ) + branchOrTagList.push_back(tag); + } + + branchOrTagList.sort(); + } + + return branchOrTagList; +} + + +bool Cervisia::IsValidTag(const QString& tag) +{ + static const QString prohibitedChars("$,.:;@"); + + if( !isalpha(tag[0].latin1()) ) + return false; + + for( uint i = 1; i < tag.length(); ++i ) + { + if( !isgraph(tag[i].latin1()) || prohibitedChars.contains(tag[i]) ) + return false; + } + + return true; +} + + +QString Cervisia::UserName() +{ + // 1. Try to retrieve the information from the control center settings + KEMailSettings settings; + QString name = settings.getSetting(KEMailSettings::RealName); + QString email = settings.getSetting(KEMailSettings::EmailAddress); + + if( name.isEmpty() || email.isEmpty() ) + { + // 2. Try to retrieve the information from the system + struct passwd* pw = getpwuid(getuid()); + if( !pw ) + return QString::null; + + char hostname[512]; + hostname[0] = '\0'; + + if( !gethostname(hostname, sizeof(hostname)) ) + hostname[sizeof(hostname)-1] = '0'; + + name = QString::fromLocal8Bit(pw->pw_gecos); + email = QString::fromLocal8Bit(pw->pw_name) + "@" + + QString::fromLocal8Bit(hostname); + } + + QString result = name; + result += " <"; + result += email; + result += ">"; + + return result; +} + + +QString Cervisia::NormalizeRepository(const QString& repository) +{ + // only :pserver: repositories + if( !repository.startsWith(":pserver:") ) + return repository; + + QRegExp rx(":pserver:(" + userNameRegExp + passwordRegExp + "@)?" + + hostNameRegExp + portRegExp + pathRegExp); + + // extract username, hostname, port and path from CVSROOT + QString userName, hostName, port, path; + if( rx.search(repository) != -1 ) + { + userName = rx.cap(2); + hostName = rx.cap(4); + port = rx.cap(6); + path = rx.cap(7); + + kdDebug() << "NormalizeRepository(): username=" << userName << endl; + kdDebug() << "NormalizeRepository(): hostname=" << hostName << endl; + kdDebug() << "NormalizeRepository(): port =" << port << endl; + kdDebug() << "NormalizeRepository(): path =" << path << endl; + + if( port.isEmpty() ) + port = "2401"; + + if( userName.isEmpty() ) + userName = KUser().loginName(); + + QString canonicalForm = ":pserver:" + userName + "@" + hostName + + ":" + port + path; + + kdDebug() << "NormalizeRepository(): canonicalForm=" << canonicalForm + << endl; + return canonicalForm; + } + else + return repository; +} + + +bool Cervisia::CheckOverwrite(const QString& fileName, QWidget* parent) +{ + bool result = true; + + QFileInfo fi(fileName); + + // does the file already exist? + if( fi.exists() ) + { + result = (KMessageBox::warningContinueCancel(parent, + i18n("A file named \"%1\" already exists. Are you sure you want to overwrite it?").arg(fileName), + i18n("Overwrite File?"), + KGuiItem(i18n("&Overwrite"), "filesave", i18n("Overwrite the file"))) == KMessageBox::Continue); + } + + return result; +} + + +QString joinLine(const QStringList &list) +{ + QString line; + for ( QStringList::ConstIterator it = list.begin(); + it != list.end(); ++it ) + { + line += KShellProcess::quote(*it); + line += " "; + } + + if (line.length() > 0) + line.truncate(line.length()-1); + + return line; +} + + +// Should be replaceable by QStringList::split +QStringList splitLine(QString line, char delim) +{ + int pos; + QStringList list; + + line = line.simplifyWhiteSpace(); + while ((pos = line.find(delim)) != -1) + { + list.append(line.left(pos)); + line = line.mid(pos+1, line.length()-pos-1); + } + if (!line.isEmpty()) + list.append(line); + return list; +} + + +const QStringList fetchBranches(CvsService_stub* cvsService, QWidget* parent) +{ + return FetchBranchesAndTags(QString::fromLatin1("branch"), cvsService, + parent); +} + + +const QStringList fetchTags(CvsService_stub* cvsService, QWidget* parent) +{ + return FetchBranchesAndTags(QString::fromLatin1("revision"), cvsService, + parent); +} + + +static QStringList *tempFiles = 0; + +void cleanupTempFiles() +{ + if (tempFiles) + { + QStringList::Iterator it; + for (it = tempFiles->begin(); it != tempFiles->end(); ++it) + QFile::remove(*it); + delete tempFiles; + } +} + + +QString tempFileName(const QString& suffix) +{ + if (!tempFiles) + tempFiles = new QStringList; + + KTempFile f(QString::null, suffix); + tempFiles->append(f.name()); + return f.name(); +} + + +int compareRevisions(const QString& rev1, const QString& rev2) +{ + const int length1(rev1.length()); + const int length2(rev2.length()); + + // compare all parts of the revision + + int startPos1(0); + int startPos2(0); + while (startPos1 < length1 && startPos2 < length2) + { + int pos1(rev1.find('.', startPos1)); + if (pos1 < 0) + pos1 = length1; + const int partLength1(pos1 - startPos1); + + int pos2(rev2.find('.', startPos2)); + if (pos2 < 0) + pos2 = length2; + const int partLength2(pos2 - startPos2); + + // if the number of digits in both parts is not equal we are ready + if (const int comp = ::compare(partLength1, partLength2)) + return comp; + + // if the parts are not equal we are ready + if (const int comp = ::compare(rev1.mid(startPos1, partLength1), + rev2.mid(startPos2, partLength2))) + return comp; + + // continue with next part + startPos1 = pos1 + 1; + startPos2 = pos2 + 1; + } + + // rev1 has more parts than rev2: rev2 < rev1 + if (startPos1 < length1) + return 1; + // rev2 has more parts than rev1: rev1 < rev2 + else if (startPos2 < length2) + return -1; + // all parts of rev1 and rev2 were compared (the number of parts is equal): rev1 == rev2 + else + return 0; +} + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/misc.h b/cervisia/misc.h new file mode 100644 index 00000000..b0d912e5 --- /dev/null +++ b/cervisia/misc.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef MISC_H +#define MISC_H + + +class QString; +class QStringList; +class QWidget; +class KConfig; +class CvsService_stub; + + +namespace Cervisia +{ + +/** + * Verifies that the passed tag name is a valid cvs tag. + */ +bool IsValidTag(const QString& tag); + +/** + * Returns the user name (real name + mail address) for the changelog entry. + */ +QString UserName(); + +/** + * This method makes sure that the cvsroot specification for a pserver repository has + * always the form: + * :pserver:[user]@[host]:[port][path] + */ +QString NormalizeRepository(const QString& repository); + +bool CheckOverwrite(const QString& fileName, QWidget* parent=0); + +} + + +QString joinLine(const QStringList &list); +QStringList splitLine(QString, char delim=' '); + +QString tempFileName(const QString& suffix); +void cleanupTempFiles(); + +const QStringList fetchBranches(CvsService_stub* cvsService, QWidget* parent); +const QStringList fetchTags(CvsService_stub* cvsService, QWidget* parent); + +/** + * Compares two revision numbers. + * + * @return -1 / 0 / 1 if rev1 is < / == / > rev2 + */ +int compareRevisions(const QString& rev1, const QString& rev2); + + +/** + * Generic compare for two objects of the same class. operator<() must + * be defined for this class. + * + * @return -1 / 0 / 1 if lhs is < / == / > rhs + */ +template<class C> +int compare(const C& lhs, const C& rhs) +{ + if (lhs < rhs) + return -1; + else if (rhs < lhs) + return 1; + else + return 0; +} + + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/move_repositories.pl b/cervisia/move_repositories.pl new file mode 100644 index 00000000..c38b352d --- /dev/null +++ b/cervisia/move_repositories.pl @@ -0,0 +1,42 @@ +#!/usr/bin/perl + +$DEBUG = 0; + +while(<>) +{ + if( /\[(Repository-.*)\]/ ) + { + # remember group section + $section = $1; + print "[$section]\n"; + next; + } + if( /\[(.*)\]/ ) + { + # clear section variable for other groups + $section = ""; + next; + } + if( /^Compression=(.*)$/ ) + { + # skip if not in repository group + if( $section eq "" ) + { + next; + } + + print STDERR "\n[$section]Compression=$1\n" if ( $DEBUG ); + print "Compression=$1\n"; + } + if( /^rsh=(.*)$/ ) + { + # skip if not in repository group + if( $section eq "" ) + { + next; + } + + print STDERR "\n[$section]rsh=$1\n" if ( $DEBUG ); + print "rsh=$1\n"; + } +} diff --git a/cervisia/overview.h b/cervisia/overview.h new file mode 100644 index 00000000..e4af7af6 --- /dev/null +++ b/cervisia/overview.h @@ -0,0 +1,15 @@ +/** + * @libdoc Cervisia Class Overview + * + * This is the class documentation for the Cervisia CVS front end. At the moment, + * the only way this code can easily be used in other applications is via the + * KPart. Hopefully it should be possible to provide more flexible facilities in + * future versions. + * + * @li @ref CervisiaPart + * A KPart that provides a reusable way to embed the Cervisia interface in an app. + * + * @li @ref CervisiaShell + * A basic main window shell that embeds the CervisiaPart to provide the standalone + * Cervisia application. + */ diff --git a/cervisia/patchoptiondlg.cpp b/cervisia/patchoptiondlg.cpp new file mode 100644 index 00000000..9e6f92d1 --- /dev/null +++ b/cervisia/patchoptiondlg.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "patchoptiondlg.h" +using Cervisia::PatchOptionDialog; + +#include <qcheckbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qradiobutton.h> +#include <qvbuttongroup.h> +#include <knuminput.h> +#include <klocale.h> + + +PatchOptionDialog::PatchOptionDialog(QWidget* parent, const char* name) + : KDialogBase(parent, name, true/*modal*/, QString::null, + Ok | Cancel | Help, Ok, true/*separator*/) +{ + QFrame* mainWidget = makeMainWidget(); + QBoxLayout* topLayout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + m_formatBtnGroup = new QVButtonGroup(i18n("Output Format"), mainWidget, ""); + topLayout->addWidget(m_formatBtnGroup); + + connect(m_formatBtnGroup, SIGNAL(clicked(int)), + this, SLOT(formatChanged(int))); + + new QRadioButton(i18n( "Context" ), m_formatBtnGroup); + new QRadioButton(i18n( "Normal" ), m_formatBtnGroup); + QRadioButton* unifiedFormatBtn = new QRadioButton(i18n( "Unified" ), m_formatBtnGroup); + unifiedFormatBtn->setChecked(true); + + QLabel* contextLinesLbl = new QLabel(i18n("&Number of context lines:"), + mainWidget); + m_contextLines = new KIntNumInput(3, mainWidget); + m_contextLines->setRange(2, 65535, 1, false); + contextLinesLbl->setBuddy(m_contextLines); + + QBoxLayout* contextLinesLayout = new QHBoxLayout(topLayout); + contextLinesLayout->addWidget(contextLinesLbl); + contextLinesLayout->addWidget(m_contextLines); + + QVButtonGroup* ignoreBtnGroup = new QVButtonGroup(i18n("Ignore Options"), mainWidget); + topLayout->addWidget(ignoreBtnGroup); + + m_blankLineChk = new QCheckBox(i18n("Ignore added or removed empty lines"), + ignoreBtnGroup); + m_spaceChangeChk = new QCheckBox(i18n("Ignore changes in the amount of whitespace"), + ignoreBtnGroup); + m_allSpaceChk = new QCheckBox(i18n("Ignore all whitespace"), ignoreBtnGroup); + m_caseChangesChk = new QCheckBox(i18n("Ignore changes in case"), ignoreBtnGroup); +} + + +PatchOptionDialog::~PatchOptionDialog() +{ +} + + +QString PatchOptionDialog::diffOptions() const +{ + QString options; + + if( m_blankLineChk->isChecked() ) + options += " -B "; + + if( m_spaceChangeChk->isChecked() ) + options += " -b "; + + if( m_allSpaceChk->isChecked() ) + options += " -w "; + + if( m_caseChangesChk->isChecked() ) + options += " -i "; + + return options; +} + + +QString PatchOptionDialog::formatOption() const +{ + switch( m_formatBtnGroup->selectedId() ) + { + case 0: return "-C " + QString::number(m_contextLines->value()); + case 1: return ""; + case 2: return "-U " + QString::number(m_contextLines->value()); + } + + return ""; +} + + +void PatchOptionDialog::formatChanged(int buttonId) +{ + bool enabled = ( buttonId == 0 || buttonId == 2 ); + m_contextLines->setEnabled(enabled); +} + +#include "patchoptiondlg.moc" diff --git a/cervisia/patchoptiondlg.h b/cervisia/patchoptiondlg.h new file mode 100644 index 00000000..64109a2b --- /dev/null +++ b/cervisia/patchoptiondlg.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef PATCHOPTIONDLG_H +#define PATCHOPTIONDLG_H + +#include <kdialogbase.h> + +class QCheckBox; +class QVButtonGroup; +class KIntNumInput; + + +namespace Cervisia +{ + + +class PatchOptionDialog : public KDialogBase +{ + Q_OBJECT + +public: + explicit PatchOptionDialog(QWidget* parent = 0, const char* name = 0); + virtual ~PatchOptionDialog(); + + QString diffOptions() const; + QString formatOption() const; + +private slots: + void formatChanged(int buttonId); + +private: + QVButtonGroup* m_formatBtnGroup; + KIntNumInput* m_contextLines; + QCheckBox* m_blankLineChk; + QCheckBox* m_allSpaceChk; + QCheckBox* m_spaceChangeChk; + QCheckBox* m_caseChangesChk; +}; + + +} + +#endif diff --git a/cervisia/pics/Makefile.am b/cervisia/pics/Makefile.am new file mode 100644 index 00000000..a4b97f06 --- /dev/null +++ b/cervisia/pics/Makefile.am @@ -0,0 +1 @@ +KDE_ICON=AUTO diff --git a/cervisia/pics/README b/cervisia/pics/README new file mode 100644 index 00000000..6d8a6916 --- /dev/null +++ b/cervisia/pics/README @@ -0,0 +1,9 @@ +The following icons are created by Marco Martin <m4rt [at] libero [dot] it> +and licensed with the GNU Lesser General Public License as published by the +Free Software Foundation, version 2.1 of the License: + +vcs_add +vcs_commit +vcs_remove +vcs_status +vcs_update diff --git a/cervisia/pics/cr16-action-vcs_add.png b/cervisia/pics/cr16-action-vcs_add.png Binary files differnew file mode 100644 index 00000000..43c13620 --- /dev/null +++ b/cervisia/pics/cr16-action-vcs_add.png diff --git a/cervisia/pics/cr16-action-vcs_commit.png b/cervisia/pics/cr16-action-vcs_commit.png Binary files differnew file mode 100644 index 00000000..71f6b3dc --- /dev/null +++ b/cervisia/pics/cr16-action-vcs_commit.png diff --git a/cervisia/pics/cr16-action-vcs_diff.png b/cervisia/pics/cr16-action-vcs_diff.png Binary files differnew file mode 100644 index 00000000..9cc127ee --- /dev/null +++ b/cervisia/pics/cr16-action-vcs_diff.png diff --git a/cervisia/pics/cr16-action-vcs_remove.png b/cervisia/pics/cr16-action-vcs_remove.png Binary files differnew file mode 100644 index 00000000..8d4a9e72 --- /dev/null +++ b/cervisia/pics/cr16-action-vcs_remove.png diff --git a/cervisia/pics/cr16-action-vcs_status.png b/cervisia/pics/cr16-action-vcs_status.png Binary files differnew file mode 100644 index 00000000..186dcd70 --- /dev/null +++ b/cervisia/pics/cr16-action-vcs_status.png diff --git a/cervisia/pics/cr16-action-vcs_update.png b/cervisia/pics/cr16-action-vcs_update.png Binary files differnew file mode 100644 index 00000000..c3ba632c --- /dev/null +++ b/cervisia/pics/cr16-action-vcs_update.png diff --git a/cervisia/pics/cr22-action-vcs_add.png b/cervisia/pics/cr22-action-vcs_add.png Binary files differnew file mode 100644 index 00000000..a083f822 --- /dev/null +++ b/cervisia/pics/cr22-action-vcs_add.png diff --git a/cervisia/pics/cr22-action-vcs_commit.png b/cervisia/pics/cr22-action-vcs_commit.png Binary files differnew file mode 100644 index 00000000..c0c96ef6 --- /dev/null +++ b/cervisia/pics/cr22-action-vcs_commit.png diff --git a/cervisia/pics/cr22-action-vcs_diff.png b/cervisia/pics/cr22-action-vcs_diff.png Binary files differnew file mode 100644 index 00000000..5c5d444b --- /dev/null +++ b/cervisia/pics/cr22-action-vcs_diff.png diff --git a/cervisia/pics/cr22-action-vcs_remove.png b/cervisia/pics/cr22-action-vcs_remove.png Binary files differnew file mode 100644 index 00000000..b08c25e3 --- /dev/null +++ b/cervisia/pics/cr22-action-vcs_remove.png diff --git a/cervisia/pics/cr22-action-vcs_status.png b/cervisia/pics/cr22-action-vcs_status.png Binary files differnew file mode 100644 index 00000000..974fbb65 --- /dev/null +++ b/cervisia/pics/cr22-action-vcs_status.png diff --git a/cervisia/pics/cr22-action-vcs_update.png b/cervisia/pics/cr22-action-vcs_update.png Binary files differnew file mode 100644 index 00000000..5be225a7 --- /dev/null +++ b/cervisia/pics/cr22-action-vcs_update.png diff --git a/cervisia/pics/cr32-action-vcs_add.png b/cervisia/pics/cr32-action-vcs_add.png Binary files differnew file mode 100644 index 00000000..5d4ae629 --- /dev/null +++ b/cervisia/pics/cr32-action-vcs_add.png diff --git a/cervisia/pics/cr32-action-vcs_commit.png b/cervisia/pics/cr32-action-vcs_commit.png Binary files differnew file mode 100644 index 00000000..ae10315e --- /dev/null +++ b/cervisia/pics/cr32-action-vcs_commit.png diff --git a/cervisia/pics/cr32-action-vcs_diff.png b/cervisia/pics/cr32-action-vcs_diff.png Binary files differnew file mode 100644 index 00000000..ee490e08 --- /dev/null +++ b/cervisia/pics/cr32-action-vcs_diff.png diff --git a/cervisia/pics/cr32-action-vcs_remove.png b/cervisia/pics/cr32-action-vcs_remove.png Binary files differnew file mode 100644 index 00000000..a4ef8905 --- /dev/null +++ b/cervisia/pics/cr32-action-vcs_remove.png diff --git a/cervisia/pics/cr32-action-vcs_status.png b/cervisia/pics/cr32-action-vcs_status.png Binary files differnew file mode 100644 index 00000000..804f5397 --- /dev/null +++ b/cervisia/pics/cr32-action-vcs_status.png diff --git a/cervisia/pics/cr32-action-vcs_update.png b/cervisia/pics/cr32-action-vcs_update.png Binary files differnew file mode 100644 index 00000000..13124fe5 --- /dev/null +++ b/cervisia/pics/cr32-action-vcs_update.png diff --git a/cervisia/pics/cr48-action-vcs_add.png b/cervisia/pics/cr48-action-vcs_add.png Binary files differnew file mode 100644 index 00000000..c0a8dee8 --- /dev/null +++ b/cervisia/pics/cr48-action-vcs_add.png diff --git a/cervisia/pics/cr48-action-vcs_commit.png b/cervisia/pics/cr48-action-vcs_commit.png Binary files differnew file mode 100644 index 00000000..aacc2b7e --- /dev/null +++ b/cervisia/pics/cr48-action-vcs_commit.png diff --git a/cervisia/pics/cr48-action-vcs_diff.png b/cervisia/pics/cr48-action-vcs_diff.png Binary files differnew file mode 100644 index 00000000..5a954ee6 --- /dev/null +++ b/cervisia/pics/cr48-action-vcs_diff.png diff --git a/cervisia/pics/cr48-action-vcs_remove.png b/cervisia/pics/cr48-action-vcs_remove.png Binary files differnew file mode 100644 index 00000000..caa2fcc4 --- /dev/null +++ b/cervisia/pics/cr48-action-vcs_remove.png diff --git a/cervisia/pics/cr48-action-vcs_status.png b/cervisia/pics/cr48-action-vcs_status.png Binary files differnew file mode 100644 index 00000000..a98d15dc --- /dev/null +++ b/cervisia/pics/cr48-action-vcs_status.png diff --git a/cervisia/pics/cr48-action-vcs_update.png b/cervisia/pics/cr48-action-vcs_update.png Binary files differnew file mode 100644 index 00000000..ab7b2d89 --- /dev/null +++ b/cervisia/pics/cr48-action-vcs_update.png diff --git a/cervisia/pics/crsc-action-vcs_add.svgz b/cervisia/pics/crsc-action-vcs_add.svgz Binary files differnew file mode 100644 index 00000000..b6e9fc62 --- /dev/null +++ b/cervisia/pics/crsc-action-vcs_add.svgz diff --git a/cervisia/pics/crsc-action-vcs_commit.svgz b/cervisia/pics/crsc-action-vcs_commit.svgz Binary files differnew file mode 100644 index 00000000..b1d63c7b --- /dev/null +++ b/cervisia/pics/crsc-action-vcs_commit.svgz diff --git a/cervisia/pics/crsc-action-vcs_diff.svgz b/cervisia/pics/crsc-action-vcs_diff.svgz Binary files differnew file mode 100644 index 00000000..45549f69 --- /dev/null +++ b/cervisia/pics/crsc-action-vcs_diff.svgz diff --git a/cervisia/pics/crsc-action-vcs_remove.svgz b/cervisia/pics/crsc-action-vcs_remove.svgz Binary files differnew file mode 100644 index 00000000..b6165bb7 --- /dev/null +++ b/cervisia/pics/crsc-action-vcs_remove.svgz diff --git a/cervisia/pics/crsc-action-vcs_status.svgz b/cervisia/pics/crsc-action-vcs_status.svgz Binary files differnew file mode 100644 index 00000000..32b1ed9d --- /dev/null +++ b/cervisia/pics/crsc-action-vcs_status.svgz diff --git a/cervisia/pics/crsc-action-vcs_update.svgz b/cervisia/pics/crsc-action-vcs_update.svgz Binary files differnew file mode 100644 index 00000000..b251d05f --- /dev/null +++ b/cervisia/pics/crsc-action-vcs_update.svgz diff --git a/cervisia/progressdlg.cpp b/cervisia/progressdlg.cpp new file mode 100644 index 00000000..d72c6565 --- /dev/null +++ b/cervisia/progressdlg.cpp @@ -0,0 +1,276 @@ +/* + * Copyright (c) 1999-2002 Bernd Gehrmann <bernd@mail.berlios.de> + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "progressdlg.h" + +#include <qlabel.h> +#include <qlayout.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qtimer.h> +#include <qvbox.h> + +#include <cvsjob_stub.h> +#include <dcopref.h> +#include <kanimwidget.h> +#include <kapplication.h> +#include <kconfig.h> + +#include "cervisiasettings.h" + + +struct ProgressDialog::Private +{ + bool isCancelled; + bool isShown; + bool hasError; + + CvsJob_stub* cvsJob; + QString buffer; + QString errorId1, errorId2; + QStringList output; + + QTimer* timer; + KAnimWidget* gear; + QListBox* resultbox; +}; + + +ProgressDialog::ProgressDialog(QWidget* parent, const QString& heading, + const DCOPRef& job, const QString& errorIndicator, + const QString& caption) + : KDialogBase(parent, 0, true, caption, Cancel, Cancel, true) + , DCOPObject() + , d(new Private) +{ + // initialize private data + d->isCancelled = false; + d->isShown = false; + d->hasError = false; + + d->cvsJob = new CvsJob_stub(job); + d->buffer = ""; + + d->errorId1 = "cvs " + errorIndicator + ":"; + d->errorId2 = "cvs [" + errorIndicator + " aborted]:"; + + setupGui(heading); +} + + +ProgressDialog::~ProgressDialog() +{ + delete d->cvsJob; + delete d; +} + + +void ProgressDialog::setupGui(const QString& heading) +{ + QVBox* vbox = makeVBoxMainWidget(); + vbox->setSpacing(10); + + QWidget* headingBox = new QWidget(vbox); + QHBoxLayout* hboxLayout = new QHBoxLayout(headingBox); + + QLabel* textLabel = new QLabel(heading, headingBox); + textLabel->setMinimumWidth(textLabel->sizeHint().width()); + textLabel->setFixedHeight(textLabel->sizeHint().height()); + hboxLayout->addWidget(textLabel); + hboxLayout->addStretch(); + + d->gear = new KAnimWidget(QString("kde"), 32, headingBox); + d->gear->setFixedSize(32, 32); + hboxLayout->addWidget(d->gear); + + d->resultbox = new QListBox(vbox); + d->resultbox->setSelectionMode(QListBox::NoSelection); + QFontMetrics fm(d->resultbox->fontMetrics()); + d->resultbox->setMinimumSize(fm.width("0")*70, fm.lineSpacing()*8); + + resize(sizeHint()); +} + + +bool ProgressDialog::execute() +{ + // get command line and display it + QString cmdLine = d->cvsJob->cvsCommand(); + d->resultbox->insertItem(cmdLine); + + // establish connections to the signals of the cvs job + connectDCOPSignal(d->cvsJob->app(), d->cvsJob->obj(), "jobExited(bool, int)", + "slotJobExited(bool, int)", true); + connectDCOPSignal(d->cvsJob->app(), d->cvsJob->obj(), "receivedStdout(QString)", + "slotReceivedOutputNonGui(QString)", true); + connectDCOPSignal(d->cvsJob->app(), d->cvsJob->obj(), "receivedStderr(QString)", + "slotReceivedOutputNonGui(QString)", true); + + // we wait for 4 seconds (or the timeout set by the user) before we + // force the dialog to show up + d->timer = new QTimer(this); + connect(d->timer, SIGNAL(timeout()), this, SLOT(slotTimeoutOccurred())); + d->timer->start(CervisiaSettings::timeout(), true); + + bool started = d->cvsJob->execute(); + if( !started ) + return false; + + QApplication::setOverrideCursor(waitCursor); + kapp->enter_loop(); + if (QApplication::overrideCursor()) + QApplication::restoreOverrideCursor(); + + return !d->isCancelled; +} + + +bool ProgressDialog::getLine(QString& line) +{ + if( d->output.isEmpty() ) + return false; + + line = d->output.first(); + d->output.remove(d->output.begin()); + + return true; +} + + +QStringList ProgressDialog::getOutput() const +{ + return d->output; +} + + +void ProgressDialog::slotReceivedOutputNonGui(QString buffer) +{ + d->buffer += buffer; + + processOutput(); + if( d->hasError ) + { + stopNonGuiPart(); + startGuiPart(); + } +} + + +void ProgressDialog::slotReceivedOutput(QString buffer) +{ + d->buffer += buffer; + processOutput(); +} + + +void ProgressDialog::slotJobExited(bool normalExit, int status) +{ + Q_UNUSED(status) + + if( !d->isShown ) + stopNonGuiPart(); + + d->gear->stop(); + if( !d->buffer.isEmpty() ) + { + d->buffer += '\n'; + processOutput(); + } + + // Close the dialog automatically if there are no + // error messages or the process has been aborted + // 'by hand' (e.g. by clicking the cancel button) + if( !d->hasError || !normalExit ) + kapp->exit_loop(); +} + + +void ProgressDialog::slotCancel() +{ + d->isCancelled = true; + + bool isRunning = d->cvsJob->isRunning(); + if( isRunning ) + d->cvsJob->cancel(); + else + kapp->exit_loop(); +} + + +void ProgressDialog::slotTimeoutOccurred() +{ + stopNonGuiPart(); + startGuiPart(); +} + + +void ProgressDialog::stopNonGuiPart() +{ + d->timer->stop(); + + disconnectDCOPSignal(d->cvsJob->app(), d->cvsJob->obj(), "receivedStdout(QString)", + "slotReceivedOutputNonGui(QString)"); + disconnectDCOPSignal(d->cvsJob->app(), d->cvsJob->obj(), "receivedStderr(QString)", + "slotReceivedOutputNonGui(QString)"); + + kapp->exit_loop(); +} + + +void ProgressDialog::startGuiPart() +{ + connectDCOPSignal(d->cvsJob->app(), d->cvsJob->obj(), "receivedStdout(QString)", + "slotReceivedOutput(QString)", true); + connectDCOPSignal(d->cvsJob->app(), d->cvsJob->obj(), "receivedStderr(QString)", + "slotReceivedOutput(QString)", true); + + show(); + d->isShown = true; + + d->gear->start(); + QApplication::restoreOverrideCursor(); + kapp->enter_loop(); +} + + +void ProgressDialog::processOutput() +{ + int pos; + while( (pos = d->buffer.find('\n')) != -1 ) + { + QString item = d->buffer.left(pos); + if( item.startsWith(d->errorId1) || + item.startsWith(d->errorId2) || + item.startsWith("cvs [server aborted]:") ) + { + d->hasError = true; + d->resultbox->insertItem(item); + } + else if( item.startsWith("cvs server:") ) + d->resultbox->insertItem(item); + else + d->output.append(item); + + // remove item from buffer + d->buffer.remove(0, pos+1); + } +} + + +#include "progressdlg.moc" diff --git a/cervisia/progressdlg.h b/cervisia/progressdlg.h new file mode 100644 index 00000000..3e9dfed1 --- /dev/null +++ b/cervisia/progressdlg.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 1999-2002 Bernd Gehrmann <bernd@mail.berlios.de> + * Copyright (c) 2002 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef PROGRESSDLG_H +#define PROGRESSDLG_H + +#include <dcopobject.h> +#include <kdialogbase.h> + +class QString; +class QWidget; +class DCOPRef; + + +class ProgressDialog : public KDialogBase, public DCOPObject +{ + K_DCOP + Q_OBJECT + +public: + ProgressDialog(QWidget* parent, const QString& heading, const DCOPRef& job, + const QString& errorIndicator, const QString& caption = ""); + ~ProgressDialog(); + + bool execute(); + bool getLine(QString& line); + QStringList getOutput() const; + +k_dcop: + void slotReceivedOutputNonGui(QString buffer); + void slotReceivedOutput(QString buffer); + void slotJobExited(bool normalExit, int status); + +protected slots: + virtual void slotCancel(); + +private slots: + void slotTimeoutOccurred(); + +private: + void setupGui(const QString& heading); + void stopNonGuiPart(); + void startGuiPart(); + void processOutput(); + + struct Private; + Private* d; +}; + + +#endif + diff --git a/cervisia/protocolview.cpp b/cervisia/protocolview.cpp new file mode 100644 index 00000000..8496b983 --- /dev/null +++ b/cervisia/protocolview.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@physik.hu-berlin.de + * Copyright (c) 2003-2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "protocolview.h" + +#include <qdir.h> +#include <qpopupmenu.h> +#include <dcopref.h> +#include <kconfig.h> +#include <klocale.h> +#include <kmessagebox.h> + +#include "cervisiapart.h" +#include "cvsjob_stub.h" + + +ProtocolView::ProtocolView(const QCString& appId, QWidget *parent, const char *name) + : QTextEdit(parent, name) + , job(0) + , m_isUpdateJob(false) +{ + setReadOnly(true); + setUndoRedoEnabled(false); + setTabChangesFocus(true); + setTextFormat(Qt::LogText); + + KConfig *config = CervisiaPart::config(); + config->setGroup("LookAndFeel"); + setFont(config->readFontEntry("ProtocolFont")); + + config->setGroup("Colors"); + QColor defaultColor = QColor(255, 130, 130); + conflictColor=config->readColorEntry("Conflict",&defaultColor); + defaultColor=QColor(130, 130, 255); + localChangeColor=config->readColorEntry("LocalChange",&defaultColor); + defaultColor=QColor(70, 210, 70); + remoteChangeColor=config->readColorEntry("RemoteChange",&defaultColor); + + // create a DCOP stub for the non-concurrent cvs job + job = new CvsJob_stub(appId, "NonConcurrentJob"); + + // establish connections to the signals of the cvs job + connectDCOPSignal(job->app(), job->obj(), "jobExited(bool, int)", + "slotJobExited(bool, int)", true); + connectDCOPSignal(job->app(), job->obj(), "receivedStdout(QString)", + "slotReceivedOutput(QString)", true); + connectDCOPSignal(job->app(), job->obj(), "receivedStderr(QString)", + "slotReceivedOutput(QString)", true); +} + + +ProtocolView::~ProtocolView() +{ + delete job; +} + + +bool ProtocolView::startJob(bool isUpdateJob) +{ + m_isUpdateJob = isUpdateJob; + + // get command line and add it to output buffer + QString cmdLine = job->cvsCommand(); + buf += cmdLine; + buf += '\n'; + processOutput(); + + // disconnect 3rd party slots from our signals + disconnect( SIGNAL(receivedLine(QString)) ); + disconnect( SIGNAL(jobFinished(bool, int)) ); + + return job->execute(); +} + + +QPopupMenu* ProtocolView::createPopupMenu(const QPoint &pos) +{ + QPopupMenu* menu = QTextEdit::createPopupMenu(pos); + + int id = menu->insertItem(i18n("Clear"), this, SLOT( clear() ), 0, -1, 0); + + if( length() == 0 ) + menu->setItemEnabled(id, false); + + return menu; +} + + +void ProtocolView::cancelJob() +{ + job->cancel(); +} + + +void ProtocolView::slotReceivedOutput(QString buffer) +{ + buf += buffer; + processOutput(); +} + + +void ProtocolView::slotJobExited(bool normalExit, int exitStatus) +{ + QString msg; + + if( normalExit ) + { + if( exitStatus ) + msg = i18n("[Exited with status %1]\n").arg(exitStatus); + else + msg = i18n("[Finished]\n"); + } + else + msg = i18n("[Aborted]\n"); + + buf += '\n'; + buf += msg; + processOutput(); + + emit jobFinished(normalExit, exitStatus); +} + + +void ProtocolView::processOutput() +{ + int pos; + while ( (pos = buf.find('\n')) != -1) + { + QString line = buf.left(pos); + if (!line.isEmpty()) + { + appendLine(line); + emit receivedLine(line); + } + buf = buf.right(buf.length()-pos-1); + } +} + + +void ProtocolView::appendLine(const QString &line) +{ + // Escape output line, so that html tags in commit + // messages aren't interpreted + const QString escapedLine = QStyleSheet::escape(line); + + // When we don't get the output from an update job then + // just add it to the text edit. + if( !m_isUpdateJob ) + { + append(escapedLine); + return; + } + + QColor color; + // Colors are the same as in UpdateViewItem::paintCell() + if (line.startsWith("C ")) + color = conflictColor; + else if (line.startsWith("M ") + || line.startsWith("A ") || line.startsWith("R ")) + color = localChangeColor; + else if (line.startsWith("P ") || line.startsWith("U ")) + color = remoteChangeColor; + + append(color.isValid() + ? QString("<font color=\"%1\"><b>%2</b></font>").arg(color.name()) + .arg(escapedLine) + : escapedLine); +} + + +#include "protocolview.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: + + diff --git a/cervisia/protocolview.h b/cervisia/protocolview.h new file mode 100644 index 00000000..0e0390d2 --- /dev/null +++ b/cervisia/protocolview.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2003-2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef PROTOCOLVIEW_H +#define PROTOCOLVIEW_H + +#include <qtextedit.h> +#include <dcopobject.h> + +class QPoint; +class QPopupMenu; +class CvsJob_stub; + + +class ProtocolView : public QTextEdit, public DCOPObject +{ + K_DCOP + Q_OBJECT + +public: + explicit ProtocolView(const QCString& appId, QWidget *parent=0, const char *name=0); + ~ProtocolView(); + + bool startJob(bool isUpdateJob = false); + +protected: + virtual QPopupMenu* createPopupMenu(const QPoint &pos); + +k_dcop: + void slotReceivedOutput(QString buffer); + void slotJobExited(bool normalExit, int exitStatus); + +signals: + void receivedLine(QString line); + void jobFinished(bool normalExit, int exitStatus); + +private slots: + void cancelJob(); + +private: + void processOutput(); + void appendLine(const QString &line); + + QString buf; + + QColor conflictColor; + QColor localChangeColor; + QColor remoteChangeColor; + + CvsJob_stub* job; + + bool m_isUpdateJob; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/qttableview.cpp b/cervisia/qttableview.cpp new file mode 100644 index 00000000..22b2993c --- /dev/null +++ b/cervisia/qttableview.cpp @@ -0,0 +1,2278 @@ +/********************************************************************** +** $Id$ +** +** Implementation of QtTableView class +** +** Created : 941115 +** +** Copyright (C) 1992-2000 Trolltech AS. All rights reserved. +** +** This file contains a class moved out of the Qt GUI Toolkit API. It +** may be used, distributed and modified without limitation. +** +**********************************************************************/ + +#include "qttableview.h" +#include "qscrollbar.h" +#include "qpainter.h" +#include "qdrawutil.h" +#include <qapplication.h> +#include <limits.h> + +enum ScrollBarDirtyFlags { + verGeometry = 0x01, + verSteps = 0x02, + verRange = 0x04, + verValue = 0x08, + horGeometry = 0x10, + horSteps = 0x20, + horRange = 0x40, + horValue = 0x80, + verMask = 0x0F, + horMask = 0xF0 +}; + + +#define HSBEXT horizontalScrollBar()->sizeHint().height() +#define VSBEXT verticalScrollBar()->sizeHint().width() + + +class QCornerSquare : public QWidget // internal class +{ +public: + QCornerSquare( QWidget *, const char* = 0 ); + void paintEvent( QPaintEvent * ); +}; + +QCornerSquare::QCornerSquare( QWidget *parent, const char *name ) + : QWidget( parent, name ) +{ +} + +void QCornerSquare::paintEvent( QPaintEvent * ) +{ +} + + +// NOT REVISED +/*! + \class QtTableView qttableview.h + \brief The QtTableView class provides an abstract base for tables. + + \obsolete + + A table view consists of a number of abstract cells organized in rows + and columns, and a visible part called a view. The cells are identified + with a row index and a column index. The top-left cell is in row 0, + column 0. + + The behavior of the widget can be finely tuned using + setTableFlags(); a typical subclass will consist of little more than a + call to setTableFlags(), some table content manipulation and an + implementation of paintCell(). Subclasses that need cells with + variable width or height must reimplement cellHeight() and/or + cellWidth(). Use updateTableSize() to tell QtTableView when the + width or height has changed. + + When you read this documentation, it is important to understand the + distinctions among the four pixel coordinate systems involved. + + \list 1 + \i The \e cell coordinates. (0,0) is the top-left corner of a cell. + Cell coordinates are used by functions such as paintCell(). + + \i The \e table coordinates. (0,0) is the top-left corner of the cell at + row 0 and column 0. These coordinates are absolute; that is, they are + independent of what part of the table is visible at the moment. They are + used by functions such as setXOffset() or maxYOffset(). + + \i The \e widget coordinates. (0,0) is the top-left corner of the widget, + \e including the frame. They are used by functions such as repaint(). + + \i The \e view coordinates. (0,0) is the top-left corner of the view, \e + excluding the frame. This is the least-used coordinate system; it is used by + functions such as viewWidth(). \endlist + + It is rather unfortunate that we have to use four different + coordinate systems, but there was no alternative to provide a flexible and + powerful base class. + + Note: The row,column indices are always given in that order, + i.e., first the vertical (row), then the horizontal (column). This is + the opposite order of all pixel operations, which take first the + horizontal (x) and then the vertical (y). + + <img src=qtablevw-m.png> <img src=qtablevw-w.png> + + \warning the functions setNumRows(), setNumCols(), setCellHeight(), + setCellWidth(), setTableFlags() and clearTableFlags() may cause + virtual functions such as cellWidth() and cellHeight() to be called, + even if autoUpdate() is FALSE. This may cause errors if relevant + state variables are not initialized. + + \warning Experience has shown that use of this widget tends to cause + more bugs than expected and our analysis indicates that the widget's + very flexibility is the problem. If QScrollView or QListBox can + easily be made to do the job you need, we recommend subclassing + those widgets rather than QtTableView. In addition, QScrollView makes + it easy to have child widgets inside tables, which QtTableView + doesn't support at all. + + \sa QScrollView + \link guibooks.html#fowler GUI Design Handbook: Table\endlink +*/ + + +/*! + Constructs a table view. The \a parent, \a name and \f arguments + are passed to the QFrame constructor. + + The \link setTableFlags() table flags\endlink are all cleared (set to 0). + Set \c Tbl_autoVScrollBar or \c Tbl_autoHScrollBar to get automatic scroll + bars and \c Tbl_clipCellPainting to get safe clipping. + + The \link setCellHeight() cell height\endlink and \link setCellWidth() + cell width\endlink are set to 0. + + Frame line shapes (QFrame::HLink and QFrame::VLine) are disallowed; + see QFrame::setFrameStyle(). + + Note that the \a f argument is \e not \link setTableFlags() table + flags \endlink but rather \link QWidget::QWidget() widget + flags. \endlink + +*/ + +QtTableView::QtTableView( QWidget *parent, const char *name, WFlags f ) + : QFrame( parent, name, f ) +{ + nRows = nCols = 0; // zero rows/cols + xCellOffs = yCellOffs = 0; // zero offset + xCellDelta = yCellDelta = 0; // zero cell offset + xOffs = yOffs = 0; // zero total pixel offset + cellH = cellW = 0; // user defined cell size + tFlags = 0; + vScrollBar = hScrollBar = 0; // no scroll bars + cornerSquare = 0; + sbDirty = 0; + eraseInPaint = FALSE; + verSliding = FALSE; + verSnappingOff = FALSE; + horSliding = FALSE; + horSnappingOff = FALSE; + coveringCornerSquare = FALSE; + inSbUpdate = FALSE; +} + +/*! + Destroys the table view. +*/ + +QtTableView::~QtTableView() +{ + delete vScrollBar; + delete hScrollBar; + delete cornerSquare; +} + + +/*! + \internal + Reimplements QWidget::setBackgroundColor() for binary compatibility. + \sa setPalette() +*/ + +void QtTableView::setBackgroundColor( const QColor &c ) +{ + QWidget::setBackgroundColor( c ); +} + +/*!\reimp +*/ + +void QtTableView::setPalette( const QPalette &p ) +{ + QWidget::setPalette( p ); +} + +/*!\reimp +*/ + +void QtTableView::show() +{ + showOrHideScrollBars(); + QWidget::show(); +} + + +/*! + \overload void QtTableView::repaint( bool erase ) + Repaints the entire view. +*/ + +/*! + Repaints the table view directly by calling paintEvent() directly + unless updates are disabled. + + Erases the view area \a (x,y,w,h) if \a erase is TRUE. Parameters \a + (x,y) are in \e widget coordinates. + + If \a w is negative, it is replaced with <code>width() - x</code>. + If \a h is negative, it is replaced with <code>height() - y</code>. + + Doing a repaint() usually is faster than doing an update(), but + calling update() many times in a row will generate a single paint + event. + + At present, QtTableView is the only widget that reimplements \link + QWidget::repaint() repaint()\endlink. It does this because by + clearing and then repainting one cell at at time, it can make the + screen flicker less than it would otherwise. */ + +void QtTableView::repaint( int x, int y, int w, int h, bool erase ) +{ + if ( !isVisible() || testWState(WState_BlockUpdates) ) + return; + if ( w < 0 ) + w = width() - x; + if ( h < 0 ) + h = height() - y; + QRect r( x, y, w, h ); + if ( r.isEmpty() ) + return; // nothing to do + QPaintEvent e( r ); + if ( erase && backgroundMode() != NoBackground ) + eraseInPaint = TRUE; // erase when painting + paintEvent( &e ); + eraseInPaint = FALSE; +} + +/*! + \overload void QtTableView::repaint( const QRect &r, bool erase ) + Replaints rectangle \a r. If \a erase is TRUE draws the background + using the palette's background. +*/ + + +/*! + \fn int QtTableView::numRows() const + Returns the number of rows in the table. + \sa numCols(), setNumRows() +*/ + +/*! + Sets the number of rows of the table to \a rows (must be non-negative). + Does not change topCell(). + + The table repaints itself automatically if autoUpdate() is set. + + \sa numCols(), setNumCols(), numRows() +*/ + +void QtTableView::setNumRows( int rows ) +{ + if ( rows < 0 ) { +#if defined(QT_CHECK_RANGE) + qWarning( "QtTableView::setNumRows: (%s) Negative argument %d.", + name( "unnamed" ), rows ); +#endif + return; + } + if ( nRows == rows ) + return; + + if ( autoUpdate() && isVisible() ) { + int oldLastVisible = lastRowVisible(); + int oldTopCell = topCell(); + nRows = rows; + if ( autoUpdate() && isVisible() && + ( oldLastVisible != lastRowVisible() || oldTopCell != topCell() ) ) + repaint( oldTopCell != topCell() ); + } else { + // Be more careful - if destructing, bad things might happen. + nRows = rows; + } + updateScrollBars( verRange ); + updateFrameSize(); +} + +/*! + \fn int QtTableView::numCols() const + Returns the number of columns in the table. + \sa numRows(), setNumCols() +*/ + +/*! + Sets the number of columns of the table to \a cols (must be non-negative). + Does not change leftCell(). + + The table repaints itself automatically if autoUpdate() is set. + + \sa numCols(), numRows(), setNumRows() +*/ + +void QtTableView::setNumCols( int cols ) +{ + if ( cols < 0 ) { +#if defined(QT_CHECK_RANGE) + qWarning( "QtTableView::setNumCols: (%s) Negative argument %d.", + name( "unnamed" ), cols ); +#endif + return; + } + if ( nCols == cols ) + return; + int oldCols = nCols; + nCols = cols; + if ( autoUpdate() && isVisible() ) { + int maxCol = lastColVisible(); + if ( maxCol >= oldCols || maxCol >= nCols ) + repaint(); + } + updateScrollBars( horRange ); + updateFrameSize(); +} + + +/*! + \fn int QtTableView::topCell() const + Returns the index of the first row in the table that is visible in + the view. The index of the first row is 0. + \sa leftCell(), setTopCell() +*/ + +/*! + Scrolls the table so that \a row becomes the top row. + The index of the very first row is 0. + \sa setYOffset(), setTopLeftCell(), setLeftCell() +*/ + +void QtTableView::setTopCell( int row ) +{ + setTopLeftCell( row, -1 ); + return; +} + +/*! + \fn int QtTableView::leftCell() const + Returns the index of the first column in the table that is visible in + the view. The index of the very leftmost column is 0. + \sa topCell(), setLeftCell() +*/ + +/*! + Scrolls the table so that \a col becomes the leftmost + column. The index of the leftmost column is 0. + \sa setXOffset(), setTopLeftCell(), setTopCell() +*/ + +void QtTableView::setLeftCell( int col ) +{ + setTopLeftCell( -1, col ); + return; +} + +/*! + Scrolls the table so that the cell at row \a row and colum \a + col becomes the top-left cell in the view. The cell at the extreme + top left of the table is at position (0,0). + \sa setLeftCell(), setTopCell(), setOffset() +*/ + +void QtTableView::setTopLeftCell( int row, int col ) +{ + int newX = xOffs; + int newY = yOffs; + + if ( col >= 0 ) { + if ( cellW ) { + newX = col*cellW; + if ( newX > maxXOffset() ) + newX = maxXOffset(); + } else { + newX = 0; + while ( col ) + newX += cellWidth( --col ); // optimize using current! ### + } + } + if ( row >= 0 ) { + if ( cellH ) { + newY = row*cellH; + if ( newY > maxYOffset() ) + newY = maxYOffset(); + } else { + newY = 0; + while ( row ) + newY += cellHeight( --row ); // optimize using current! ### + } + } + setOffset( newX, newY ); +} + + +/*! + \fn int QtTableView::xOffset() const + + Returns the x coordinate in \e table coordinates of the pixel that is + currently on the left edge of the view. + + \sa setXOffset(), yOffset(), leftCell() */ + +/*! + Scrolls the table so that \a x becomes the leftmost pixel in the view. + The \a x parameter is in \e table coordinates. + + The interaction with \link setTableFlags() Tbl_snapToHGrid + \endlink is tricky. + + \sa xOffset(), setYOffset(), setOffset(), setLeftCell() +*/ + +void QtTableView::setXOffset( int x ) +{ + setOffset( x, yOffset() ); +} + +/*! + \fn int QtTableView::yOffset() const + + Returns the y coordinate in \e table coordinates of the pixel that is + currently on the top edge of the view. + + \sa setYOffset(), xOffset(), topCell() +*/ + + +/*! + Scrolls the table so that \a y becomes the top pixel in the view. + The \a y parameter is in \e table coordinates. + + The interaction with \link setTableFlags() Tbl_snapToVGrid + \endlink is tricky. + + \sa yOffset(), setXOffset(), setOffset(), setTopCell() +*/ + +void QtTableView::setYOffset( int y ) +{ + setOffset( xOffset(), y ); +} + +/*! + Scrolls the table so that \a (x,y) becomes the top-left pixel + in the view. Parameters \a (x,y) are in \e table coordinates. + + The interaction with \link setTableFlags() Tbl_snapTo*Grid \endlink + is tricky. If \a updateScrBars is TRUE, the scroll bars are + updated. + + \sa xOffset(), yOffset(), setXOffset(), setYOffset(), setTopLeftCell() +*/ + +void QtTableView::setOffset( int x, int y, bool updateScrBars ) +{ + if ( (!testTableFlags(Tbl_snapToHGrid) || xCellDelta == 0) && + (!testTableFlags(Tbl_snapToVGrid) || yCellDelta == 0) && + (x == xOffs && y == yOffs) ) + return; + + if ( x < 0 ) + x = 0; + if ( y < 0 ) + y = 0; + + if ( cellW ) { + if ( x > maxXOffset() ) + x = maxXOffset(); + xCellOffs = x / cellW; + if ( !testTableFlags(Tbl_snapToHGrid) ) { + xCellDelta = (short)(x % cellW); + } else { + x = xCellOffs*cellW; + xCellDelta = 0; + } + } else { + int xn=0, xcd=0, col = 0; + while ( col < nCols-1 && x >= xn+(xcd=cellWidth(col)) ) { + xn += xcd; + col++; + } + xCellOffs = col; + if ( testTableFlags(Tbl_snapToHGrid) ) { + xCellDelta = 0; + x = xn; + } else { + xCellDelta = (short)(x-xn); + } + } + if ( cellH ) { + if ( y > maxYOffset() ) + y = maxYOffset(); + yCellOffs = y / cellH; + if ( !testTableFlags(Tbl_snapToVGrid) ) { + yCellDelta = (short)(y % cellH); + } else { + y = yCellOffs*cellH; + yCellDelta = 0; + } + } else { + int yn=0, yrd=0, row=0; + while ( row < nRows-1 && y >= yn+(yrd=cellHeight(row)) ) { + yn += yrd; + row++; + } + yCellOffs = row; + if ( testTableFlags(Tbl_snapToVGrid) ) { + yCellDelta = 0; + y = yn; + } else { + yCellDelta = (short)(y-yn); + } + } + int dx = (x - xOffs); + int dy = (y - yOffs); + xOffs = x; + yOffs = y; + if ( autoUpdate() && isVisible() ) + scroll( dx, dy ); + if ( updateScrBars ) + updateScrollBars( verValue | horValue ); +} + + +/*! + \overload int QtTableView::cellWidth() const + + Returns the column width in pixels. Returns 0 if the columns have + variable widths. + + \sa setCellWidth(), cellHeight() +*/ + +/*! + Returns the width of column \a col in pixels. + + This function is virtual and must be reimplemented by subclasses that + have variable cell widths. Note that if the total table width + changes, updateTableSize() must be called. + + \sa setCellWidth(), cellHeight(), totalWidth(), updateTableSize() +*/ + +int QtTableView::cellWidth( int ) +{ + return cellW; +} + + +/*! + Sets the width in pixels of the table cells to \a cellWidth. + + Setting it to 0 means that the column width is variable. When + set to 0 (this is the default) QtTableView calls the virtual function + cellWidth() to get the width. + + \sa cellWidth(), setCellHeight(), totalWidth(), numCols() +*/ + +void QtTableView::setCellWidth( int cellWidth ) +{ + if ( cellW == cellWidth ) + return; +#if defined(QT_CHECK_RANGE) + if ( cellWidth < 0 || cellWidth > SHRT_MAX ) { + qWarning( "QtTableView::setCellWidth: (%s) Argument out of range (%d)", + name( "unnamed" ), cellWidth ); + return; + } +#endif + cellW = (short)cellWidth; + + updateScrollBars( horSteps | horRange ); + if ( autoUpdate() && isVisible() ) + repaint(); + +} + +/*! + \overload int QtTableView::cellHeight() const + + Returns the row height, in pixels. Returns 0 if the rows have + variable heights. + + \sa setCellHeight(), cellWidth() +*/ + + +/*! + Returns the height of row \a row in pixels. + + This function is virtual and must be reimplemented by subclasses that + have variable cell heights. Note that if the total table height + changes, updateTableSize() must be called. + + \sa setCellHeight(), cellWidth(), totalHeight() +*/ + +int QtTableView::cellHeight( int ) +{ + return cellH; +} + +/*! + Sets the height in pixels of the table cells to \a cellHeight. + + Setting it to 0 means that the row height is variable. When set + to 0 (this is the default), QtTableView calls the virtual function + cellHeight() to get the height. + + \sa cellHeight(), setCellWidth(), totalHeight(), numRows() +*/ + +void QtTableView::setCellHeight( int cellHeight ) +{ + if ( cellH == cellHeight ) + return; +#if defined(QT_CHECK_RANGE) + if ( cellHeight < 0 || cellHeight > SHRT_MAX ) { + qWarning( "QtTableView::setCellHeight: (%s) Argument out of range (%d)", + name( "unnamed" ), cellHeight ); + return; + } +#endif + cellH = (short)cellHeight; + if ( autoUpdate() && isVisible() ) + repaint(); + updateScrollBars( verSteps | verRange ); +} + + +/*! + Returns the total width of the table in pixels. + + This function is virtual and should be reimplemented by subclasses that + have variable cell widths and a non-trivial cellWidth() function, or a + large number of columns in the table. + + The default implementation may be slow for very wide tables. + + \sa cellWidth(), totalHeight() */ + +int QtTableView::totalWidth() +{ + if ( cellW ) { + return cellW*nCols; + } else { + int tw = 0; + for( int i = 0 ; i < nCols ; i++ ) + tw += cellWidth( i ); + return tw; + } +} + +/*! + Returns the total height of the table in pixels. + + This function is virtual and should be reimplemented by subclasses that + have variable cell heights and a non-trivial cellHeight() function, or a + large number of rows in the table. + + The default implementation may be slow for very tall tables. + + \sa cellHeight(), totalWidth() +*/ + +int QtTableView::totalHeight() +{ + if ( cellH ) { + return cellH*nRows; + } else { + int th = 0; + for( int i = 0 ; i < nRows ; i++ ) + th += cellHeight( i ); + return th; + } +} + + +/*! + \fn uint QtTableView::tableFlags() const + + Returns the union of the table flags that are currently set. + + \sa setTableFlags(), clearTableFlags(), testTableFlags() +*/ + +/*! + \fn bool QtTableView::testTableFlags( uint f ) const + + Returns TRUE if any of the table flags in \a f are currently set, + otherwise FALSE. + + \sa setTableFlags(), clearTableFlags(), tableFlags() +*/ + +/*! + Sets the table flags to \a f. + + If a flag setting changes the appearance of the table, the table is + repainted if - and only if - autoUpdate() is TRUE. + + The table flags are mostly single bits, though there are some multibit + flags for convenience. Here is a complete list: + + <dl compact> + <dt> Tbl_vScrollBar <dd> - The table has a vertical scroll bar. + <dt> Tbl_hScrollBar <dd> - The table has a horizontal scroll bar. + <dt> Tbl_autoVScrollBar <dd> - The table has a vertical scroll bar if + - and only if - the table is taller than the view. + <dt> Tbl_autoHScrollBar <dd> The table has a horizontal scroll bar if + - and only if - the table is wider than the view. + <dt> Tbl_autoScrollBars <dd> - The union of the previous two flags. + <dt> Tbl_clipCellPainting <dd> - The table uses QPainter::setClipRect() to + make sure that paintCell() will not draw outside the cell + boundaries. + <dt> Tbl_cutCellsV <dd> - The table will never show part of a + cell at the bottom of the table; if there is not space for all of + a cell, the space is left blank. + <dt> Tbl_cutCellsH <dd> - The table will never show part of a + cell at the right side of the table; if there is not space for all of + a cell, the space is left blank. + <dt> Tbl_cutCells <dd> - The union of the previous two flags. + <dt> Tbl_scrollLastHCell <dd> - When the user scrolls horizontally, + let him/her scroll the last cell left until it is at the left + edge of the view. If this flag is not set, the user can only scroll + to the point where the last cell is completely visible. + <dt> Tbl_scrollLastVCell <dd> - When the user scrolls vertically, let + him/her scroll the last cell up until it is at the top edge of + the view. If this flag is not set, the user can only scroll to the + point where the last cell is completely visible. + <dt> Tbl_scrollLastCell <dd> - The union of the previous two flags. + <dt> Tbl_smoothHScrolling <dd> - The table scrolls as smoothly as + possible when the user scrolls horizontally. When this flag is not + set, scrolling is done one cell at a time. + <dt> Tbl_smoothVScrolling <dd> - The table scrolls as smoothly as + possible when scrolling vertically. When this flag is not set, + scrolling is done one cell at a time. + <dt> Tbl_smoothScrolling <dd> - The union of the previous two flags. + <dt> Tbl_snapToHGrid <dd> - Except when the user is actually scrolling, + the leftmost column shown snaps to the leftmost edge of the view. + <dt> Tbl_snapToVGrid <dd> - Except when the user is actually + scrolling, the top row snaps to the top edge of the view. + <dt> Tbl_snapToGrid <dd> - The union of the previous two flags. + </dl> + + You can specify more than one flag at a time using bitwise OR. + + Example: + \code + setTableFlags( Tbl_smoothScrolling | Tbl_autoScrollBars ); + \endcode + + \warning The cutCells options (\c Tbl_cutCells, \c Tbl_cutCellsH and + Tbl_cutCellsV) may cause painting problems when scrollbars are + enabled. Do not combine cutCells and scrollbars. + + + \sa clearTableFlags(), testTableFlags(), tableFlags() +*/ + +void QtTableView::setTableFlags( uint f ) +{ + f = (f ^ tFlags) & f; // clear flags already set + tFlags |= f; + + bool updateOn = autoUpdate(); + setAutoUpdate( FALSE ); + + uint repaintMask = Tbl_cutCellsV | Tbl_cutCellsH; + + if ( f & Tbl_vScrollBar ) { + setVerScrollBar( TRUE ); + } + if ( f & Tbl_hScrollBar ) { + setHorScrollBar( TRUE ); + } + if ( f & Tbl_autoVScrollBar ) { + updateScrollBars( verRange ); + } + if ( f & Tbl_autoHScrollBar ) { + updateScrollBars( horRange ); + } + if ( f & Tbl_scrollLastHCell ) { + updateScrollBars( horRange ); + } + if ( f & Tbl_scrollLastVCell ) { + updateScrollBars( verRange ); + } + if ( f & Tbl_snapToHGrid ) { + updateScrollBars( horRange ); + } + if ( f & Tbl_snapToVGrid ) { + updateScrollBars( verRange ); + } + if ( f & Tbl_snapToGrid ) { // Note: checks for 2 flags + if ( (f & Tbl_snapToHGrid) != 0 && xCellDelta != 0 || //have to scroll? + (f & Tbl_snapToVGrid) != 0 && yCellDelta != 0 ) { + snapToGrid( (f & Tbl_snapToHGrid) != 0, // do snapping + (f & Tbl_snapToVGrid) != 0 ); + repaintMask |= Tbl_snapToGrid; // repaint table + } + } + + if ( updateOn ) { + setAutoUpdate( TRUE ); + updateScrollBars(); + if ( isVisible() && (f & repaintMask) ) + repaint(); + } + +} + +/*! + Clears the \link setTableFlags() table flags\endlink that are set + in \a f. + + Example (clears a single flag): + \code + clearTableFlags( Tbl_snapToGrid ); + \endcode + + The default argument clears all flags. + + \sa setTableFlags(), testTableFlags(), tableFlags() +*/ + +void QtTableView::clearTableFlags( uint f ) +{ + f = (f ^ ~tFlags) & f; // clear flags that are already 0 + tFlags &= ~f; + + bool updateOn = autoUpdate(); + setAutoUpdate( FALSE ); + + uint repaintMask = Tbl_cutCellsV | Tbl_cutCellsH; + + if ( f & Tbl_vScrollBar ) { + setVerScrollBar( FALSE ); + } + if ( f & Tbl_hScrollBar ) { + setHorScrollBar( FALSE ); + } + if ( f & Tbl_scrollLastHCell ) { + int maxX = maxXOffset(); + if ( xOffs > maxX ) { + setOffset( maxX, yOffs ); + repaintMask |= Tbl_scrollLastHCell; + } + updateScrollBars( horRange ); + } + if ( f & Tbl_scrollLastVCell ) { + int maxY = maxYOffset(); + if ( yOffs > maxY ) { + setOffset( xOffs, maxY ); + repaintMask |= Tbl_scrollLastVCell; + } + updateScrollBars( verRange ); + } + if ( f & Tbl_smoothScrolling ) { // Note: checks for 2 flags + if ((f & Tbl_smoothHScrolling) != 0 && xCellDelta != 0 ||//must scroll? + (f & Tbl_smoothVScrolling) != 0 && yCellDelta != 0 ) { + snapToGrid( (f & Tbl_smoothHScrolling) != 0, // do snapping + (f & Tbl_smoothVScrolling) != 0 ); + repaintMask |= Tbl_smoothScrolling; // repaint table + } + } + if ( f & Tbl_snapToHGrid ) { + updateScrollBars( horRange ); + } + if ( f & Tbl_snapToVGrid ) { + updateScrollBars( verRange ); + } + if ( updateOn ) { + setAutoUpdate( TRUE ); + updateScrollBars(); // returns immediately if nothing to do + if ( isVisible() && (f & repaintMask) ) + repaint(); + } + +} + + +/*! + \fn bool QtTableView::autoUpdate() const + + Returns TRUE if the view updates itself automatically whenever it + is changed in some way. + + \sa setAutoUpdate() +*/ + +/*! + Sets the auto-update option of the table view to \a enable. + + If \a enable is TRUE (this is the default), the view updates itself + automatically whenever it has changed in some way (for example, when a + \link setTableFlags() flag\endlink is changed). + + If \a enable is FALSE, the view does NOT repaint itself or update + its internal state variables when it is changed. This can be + useful to avoid flicker during large changes and is singularly + useless otherwise. Disable auto-update, do the changes, re-enable + auto-update and call repaint(). + + \warning Do not leave the view in this state for a long time + (i.e., between events). If, for example, the user interacts with the + view when auto-update is off, strange things can happen. + + Setting auto-update to TRUE does not repaint the view; you must call + repaint() to do this. + + \sa autoUpdate(), repaint() +*/ + +void QtTableView::setAutoUpdate( bool enable ) +{ + if ( isUpdatesEnabled() == enable ) + return; + setUpdatesEnabled( enable ); + if ( enable ) { + showOrHideScrollBars(); + updateScrollBars(); + } +} + + +/*! + Repaints the cell at row \a row, column \a col if it is inside the view. + + If \a erase is TRUE, the relevant part of the view is cleared to the + background color/pixmap before the contents are repainted. + + \sa isVisible() +*/ + +void QtTableView::updateCell( int row, int col, bool erase ) +{ + int xPos, yPos; + if ( !colXPos( col, &xPos ) ) + return; + if ( !rowYPos( row, &yPos ) ) + return; + QRect uR = QRect( xPos, yPos, + cellW ? cellW : cellWidth(col), + cellH ? cellH : cellHeight(row) ); + repaint( uR.intersect(viewRect()), erase ); +} + + +/*! + \fn QRect QtTableView::cellUpdateRect() const + + This function should be called only from the paintCell() function in + subclasses. It returns the portion of a cell that actually needs to be + updated in \e cell coordinates. This is useful only for non-trivial + paintCell(). + +*/ + +/*! + Returns the rectangle that is the actual table, excluding any + frame, in \e widget coordinates. +*/ + +QRect QtTableView::viewRect() const +{ + return QRect( frameWidth(), frameWidth(), viewWidth(), viewHeight() ); +} + + +/*! + Returns the index of the last (bottom) row in the view. + The index of the first row is 0. + + If no rows are visible it returns -1. This can happen if the + view is too small for the first row and Tbl_cutCellsV is set. + + \sa lastColVisible() +*/ + +int QtTableView::lastRowVisible() const +{ + int cellMaxY; + int row = findRawRow( maxViewY(), &cellMaxY ); + if ( row == -1 || row >= nRows ) { // maxViewY() past end? + row = nRows - 1; // yes: return last row + } else { + if ( testTableFlags(Tbl_cutCellsV) && cellMaxY > maxViewY() ) { + if ( row == yCellOffs ) // cut by right margin? + return -1; // yes, nothing in the view + else + row = row - 1; // cut by margin, one back + } + } + return row; +} + +/*! + Returns the index of the last (right) column in the view. + The index of the first column is 0. + + If no columns are visible it returns -1. This can happen if the + view is too narrow for the first column and Tbl_cutCellsH is set. + + \sa lastRowVisible() +*/ + +int QtTableView::lastColVisible() const +{ + int cellMaxX; + int col = findRawCol( maxViewX(), &cellMaxX ); + if ( col == -1 || col >= nCols ) { // maxViewX() past end? + col = nCols - 1; // yes: return last col + } else { + if ( testTableFlags(Tbl_cutCellsH) && cellMaxX > maxViewX() ) { + if ( col == xCellOffs ) // cut by bottom margin? + return -1; // yes, nothing in the view + else + col = col - 1; // cell by margin, one back + } + } + return col; +} + +/*! + Returns TRUE if \a row is at least partially visible. + \sa colIsVisible() +*/ + +bool QtTableView::rowIsVisible( int row ) const +{ + return rowYPos( row, 0 ); +} + +/*! + Returns TRUE if \a col is at least partially visible. + \sa rowIsVisible() +*/ + +bool QtTableView::colIsVisible( int col ) const +{ + return colXPos( col, 0 ); +} + + +/*! + \internal + Called when both scroll bars are active at the same time. Covers the + bottom left corner between the two scroll bars with an empty widget. +*/ + +void QtTableView::coverCornerSquare( bool enable ) +{ + coveringCornerSquare = enable; + if ( !cornerSquare && enable ) { + cornerSquare = new QCornerSquare( this ); + Q_CHECK_PTR( cornerSquare ); + cornerSquare->setGeometry( maxViewX() + frameWidth() + 1, + maxViewY() + frameWidth() + 1, + VSBEXT, + HSBEXT); + } + if ( autoUpdate() && cornerSquare ) { + if ( enable ) + cornerSquare->show(); + else + cornerSquare->hide(); + } +} + + +/*! + \internal + Scroll the view to a position such that: + + If \a horizontal is TRUE, the leftmost column shown fits snugly + with the left edge of the view. + + If \a vertical is TRUE, the top row shown fits snugly with the top + of the view. + + You can achieve the same effect automatically by setting any of the + \link setTableFlags() Tbl_snapTo*Grid \endlink table flags. +*/ + +void QtTableView::snapToGrid( bool horizontal, bool vertical ) +{ + int newXCell = -1; + int newYCell = -1; + if ( horizontal && xCellDelta != 0 ) { + int w = cellW ? cellW : cellWidth( xCellOffs ); + if ( xCellDelta >= w/2 ) + newXCell = xCellOffs + 1; + else + newXCell = xCellOffs; + } + if ( vertical && yCellDelta != 0 ) { + int h = cellH ? cellH : cellHeight( yCellOffs ); + if ( yCellDelta >= h/2 ) + newYCell = yCellOffs + 1; + else + newYCell = yCellOffs; + } + setTopLeftCell( newYCell, newXCell ); //row,column +} + +/*! + \internal + This internal slot is connected to the horizontal scroll bar's + QScrollBar::valueChanged() signal. + + Moves the table horizontally to offset \a val without updating the + scroll bar. +*/ + +void QtTableView::horSbValue( int val ) +{ + if ( horSliding ) { + horSliding = FALSE; + if ( horSnappingOff ) { + horSnappingOff = FALSE; + tFlags |= Tbl_snapToHGrid; + } + } + setOffset( val, yOffs, FALSE ); +} + +/*! + \internal + This internal slot is connected to the horizontal scroll bar's + QScrollBar::sliderMoved() signal. + + Scrolls the table smoothly horizontally even if \c Tbl_snapToHGrid is set. +*/ + +void QtTableView::horSbSliding( int val ) +{ + if ( testTableFlags(Tbl_snapToHGrid) && + testTableFlags(Tbl_smoothHScrolling) ) { + tFlags &= ~Tbl_snapToHGrid; // turn off snapping while sliding + setOffset( val, yOffs, FALSE ); + tFlags |= Tbl_snapToHGrid; // turn on snapping again + } else { + setOffset( val, yOffs, FALSE ); + } +} + +/*! + \internal + This internal slot is connected to the horizontal scroll bar's + QScrollBar::sliderReleased() signal. +*/ + +void QtTableView::horSbSlidingDone( ) +{ + if ( testTableFlags(Tbl_snapToHGrid) && + testTableFlags(Tbl_smoothHScrolling) ) + snapToGrid( TRUE, FALSE ); +} + +/*! + \internal + This internal slot is connected to the vertical scroll bar's + QScrollBar::valueChanged() signal. + + Moves the table vertically to offset \a val without updating the + scroll bar. +*/ + +void QtTableView::verSbValue( int val ) +{ + if ( verSliding ) { + verSliding = FALSE; + if ( verSnappingOff ) { + verSnappingOff = FALSE; + tFlags |= Tbl_snapToVGrid; + } + } + setOffset( xOffs, val, FALSE ); +} + +/*! + \internal + This internal slot is connected to the vertical scroll bar's + QScrollBar::sliderMoved() signal. + + Scrolls the table smoothly vertically even if \c Tbl_snapToVGrid is set. +*/ + +void QtTableView::verSbSliding( int val ) +{ + if ( testTableFlags(Tbl_snapToVGrid) && + testTableFlags(Tbl_smoothVScrolling) ) { + tFlags &= ~Tbl_snapToVGrid; // turn off snapping while sliding + setOffset( xOffs, val, FALSE ); + tFlags |= Tbl_snapToVGrid; // turn on snapping again + } else { + setOffset( xOffs, val, FALSE ); + } +} + +/*! + \internal + This internal slot is connected to the vertical scroll bar's + QScrollBar::sliderReleased() signal. +*/ + +void QtTableView::verSbSlidingDone( ) +{ + if ( testTableFlags(Tbl_snapToVGrid) && + testTableFlags(Tbl_smoothVScrolling) ) + snapToGrid( FALSE, TRUE ); +} + + +/*! + This virtual function is called before painting of table cells + is started. It can be reimplemented by subclasses that want to + to set up the painter in a special way and that do not want to + do so for each cell. +*/ + +void QtTableView::setupPainter( QPainter * ) +{ +} + +/*! + \fn void QtTableView::paintCell( QPainter *p, int row, int col ) + + This pure virtual function is called to paint the single cell at \a + (row,col) using \a p, which is open when paintCell() is called and + must remain open. + + The coordinate system is \link QPainter::translate() translated \endlink + so that the origin is at the top-left corner of the cell to be + painted, i.e. \e cell coordinates. Do not scale or shear the coordinate + system (or if you do, restore the transformation matrix before you + return). + + The painter is not clipped by default and for maximum efficiency. For safety, + call setTableFlags(Tbl_clipCellPainting) to enable clipping. + + \sa paintEvent(), setTableFlags() */ + + +/*! + Handles paint events, \a e, for the table view. + + Calls paintCell() for the cells that needs to be repainted. +*/ + +void QtTableView::paintEvent( QPaintEvent *e ) +{ + QRect updateR = e->rect(); // update rectangle + if ( sbDirty ) { + bool e = eraseInPaint; + updateScrollBars(); + eraseInPaint = e; + } + + QPainter paint( this ); + + if ( !contentsRect().contains( updateR, TRUE ) ) {// update frame ? + drawFrame( &paint ); + if ( updateR.left() < frameWidth() ) //### + updateR.setLeft( frameWidth() ); + if ( updateR.top() < frameWidth() ) + updateR.setTop( frameWidth() ); + } + + int maxWX = maxViewX(); + int maxWY = maxViewY(); + if ( updateR.right() > maxWX ) + updateR.setRight( maxWX ); + if ( updateR.bottom() > maxWY ) + updateR.setBottom( maxWY ); + + setupPainter( &paint ); // prepare for painting table + + int firstRow = findRow( updateR.y() ); + int firstCol = findCol( updateR.x() ); + int xStart, yStart; + if ( !colXPos( firstCol, &xStart ) || !rowYPos( firstRow, &yStart ) ) { + paint.eraseRect( updateR ); // erase area outside cells but in view + return; + } + int maxX = updateR.right(); + int maxY = updateR.bottom(); + int row = firstRow; + int col; + int yPos = yStart; + int xPos = maxX+1; // in case the while() is empty + int nextX; + int nextY; + QRect winR = viewRect(); + QRect cellR; + QRect cellUR; +#ifndef QT_NO_TRANSFORMATIONS + QWMatrix matrix; +#endif + + while ( yPos <= maxY && row < nRows ) { + nextY = yPos + (cellH ? cellH : cellHeight( row )); + if ( testTableFlags( Tbl_cutCellsV ) && nextY > ( maxWY + 1 ) ) + break; + col = firstCol; + xPos = xStart; + while ( xPos <= maxX && col < nCols ) { + nextX = xPos + (cellW ? cellW : cellWidth( col )); + if ( testTableFlags( Tbl_cutCellsH ) && nextX > ( maxWX + 1 ) ) + break; + + cellR.setRect( xPos, yPos, cellW ? cellW : cellWidth(col), + cellH ? cellH : cellHeight(row) ); + cellUR = cellR.intersect( updateR ); + if ( cellUR.isValid() ) { + cellUpdateR = cellUR; + cellUpdateR.moveBy( -xPos, -yPos ); // cell coordinates + if ( eraseInPaint ) + paint.eraseRect( cellUR ); + +#ifndef QT_NO_TRANSFORMATIONS + matrix.translate( xPos, yPos ); + paint.setWorldMatrix( matrix ); + if ( testTableFlags(Tbl_clipCellPainting) || + frameWidth() > 0 && !winR.contains( cellR ) ) { //##arnt + paint.setClipRect( cellUR ); + paintCell( &paint, row, col ); + paint.setClipping( FALSE ); + } else { + paintCell( &paint, row, col ); + } + matrix.reset(); + paint.setWorldMatrix( matrix ); +#else + paint.translate( xPos, yPos ); + if ( testTableFlags(Tbl_clipCellPainting) || + frameWidth() > 0 && !winR.contains( cellR ) ) { //##arnt + paint.setClipRect( cellUR ); + paintCell( &paint, row, col ); + paint.setClipping( FALSE ); + } else { + paintCell( &paint, row, col ); + } + paint.translate( -xPos, -yPos ); +#endif + } + col++; + xPos = nextX; + } + row++; + yPos = nextY; + } + + // while painting we have to erase any areas in the view that + // are not covered by cells but are covered by the paint event + // rectangle these must be erased. We know that xPos is the last + // x pixel updated + 1 and that yPos is the last y pixel updated + 1. + + // Note that this needs to be done regardless whether we do + // eraseInPaint or not. Reason: a subclass may implement + // flicker-freeness and encourage the use of repaint(FALSE). + // The subclass, however, cannot draw all pixels, just those + // inside the cells. So QtTableView is reponsible for all pixels + // outside the cells. + + QRect viewR = viewRect(); + const QColorGroup g = colorGroup(); + + if ( xPos <= maxX ) { + QRect r = viewR; + r.setLeft( xPos ); + r.setBottom( yPos<maxY?yPos:maxY ); + if ( inherits( "QMultiLineEdit" ) ) + paint.fillRect( r.intersect( updateR ), g.base() ); + else + paint.eraseRect( r.intersect( updateR ) ); + } + if ( yPos <= maxY ) { + QRect r = viewR; + r.setTop( yPos ); + if ( inherits( "QMultiLineEdit" ) ) + paint.fillRect( r.intersect( updateR ), g.base() ); + else + paint.eraseRect( r.intersect( updateR ) ); + } +} + +/*!\reimp +*/ +void QtTableView::resizeEvent( QResizeEvent * ) +{ + updateScrollBars( horValue | verValue | horSteps | horGeometry | horRange | + verSteps | verGeometry | verRange ); + showOrHideScrollBars(); + updateFrameSize(); + int maxX = QMIN( xOffs, maxXOffset() ); // ### can be slow + int maxY = QMIN( yOffs, maxYOffset() ); + setOffset( maxX, maxY ); +} + +void QtTableView::wheelEvent( QWheelEvent * e ) +{ + if( e->orientation() == Vertical && vScrollBar && vScrollBar->isVisible() ) + QApplication::sendEvent( vScrollBar, e ); +} + +/*! + Redraws all visible cells in the table view. +*/ + +void QtTableView::updateView() +{ + repaint( viewRect() ); +} + +/*! + Returns a pointer to the vertical scroll bar mainly so you can + connect() to its signals. Note that the scroll bar works in pixel + values; use findRow() to translate to cell numbers. +*/ + +QScrollBar *QtTableView::verticalScrollBar() const +{ + QtTableView *that = (QtTableView*)this; // semantic const + if ( !vScrollBar ) { + QScrollBar *sb = new QScrollBar( QScrollBar::Vertical, that ); +#ifndef QT_NO_CURSOR + sb->setCursor( arrowCursor ); +#endif + sb->resize( sb->sizeHint() ); // height is irrelevant + Q_CHECK_PTR(sb); + sb->setTracking( FALSE ); + sb->setFocusPolicy( NoFocus ); + connect( sb, SIGNAL(valueChanged(int)), + SLOT(verSbValue(int))); + connect( sb, SIGNAL(sliderMoved(int)), + SLOT(verSbSliding(int))); + connect( sb, SIGNAL(sliderReleased()), + SLOT(verSbSlidingDone())); + sb->hide(); + that->vScrollBar = sb; + return sb; + } + return vScrollBar; +} + +/*! + Returns a pointer to the horizontal scroll bar mainly so you can + connect() to its signals. Note that the scroll bar works in pixel + values; use findCol() to translate to cell numbers. +*/ + +QScrollBar *QtTableView::horizontalScrollBar() const +{ + QtTableView *that = (QtTableView*)this; // semantic const + if ( !hScrollBar ) { + QScrollBar *sb = new QScrollBar( QScrollBar::Horizontal, that ); +#ifndef QT_NO_CURSOR + sb->setCursor( arrowCursor ); +#endif + sb->resize( sb->sizeHint() ); // width is irrelevant + sb->setFocusPolicy( NoFocus ); + Q_CHECK_PTR(sb); + sb->setTracking( FALSE ); + connect( sb, SIGNAL(valueChanged(int)), + SLOT(horSbValue(int))); + connect( sb, SIGNAL(sliderMoved(int)), + SLOT(horSbSliding(int))); + connect( sb, SIGNAL(sliderReleased()), + SLOT(horSbSlidingDone())); + sb->hide(); + that->hScrollBar = sb; + return sb; + } + return hScrollBar; +} + +/*! + Enables or disables the horizontal scroll bar, as required by + setAutoUpdate() and the \link setTableFlags() table flags\endlink. +*/ + +void QtTableView::setHorScrollBar( bool on, bool update ) +{ + if ( on ) { + tFlags |= Tbl_hScrollBar; + horizontalScrollBar(); // created + if ( update ) + updateScrollBars( horMask | verMask ); + else + sbDirty = sbDirty | (horMask | verMask); + if ( testTableFlags( Tbl_vScrollBar ) ) + coverCornerSquare( TRUE ); + if ( autoUpdate() ) + sbDirty = sbDirty | horMask; + } else { + tFlags &= ~Tbl_hScrollBar; + if ( !hScrollBar ) + return; + coverCornerSquare( FALSE ); + bool hideScrollBar = autoUpdate() && hScrollBar->isVisible(); + if ( hideScrollBar ) + hScrollBar->hide(); + if ( update ) + updateScrollBars( verMask ); + else + sbDirty = sbDirty | verMask; + if ( hideScrollBar && isVisible() ) + repaint( hScrollBar->x(), hScrollBar->y(), + width() - hScrollBar->x(), hScrollBar->height() ); + } + if ( update ) + updateFrameSize(); +} + + +/*! + Enables or disables the vertical scroll bar, as required by + setAutoUpdate() and the \link setTableFlags() table flags\endlink. +*/ + +void QtTableView::setVerScrollBar( bool on, bool update ) +{ + if ( on ) { + tFlags |= Tbl_vScrollBar; + verticalScrollBar(); // created + if ( update ) + updateScrollBars( verMask | horMask ); + else + sbDirty = sbDirty | (horMask | verMask); + if ( testTableFlags( Tbl_hScrollBar ) ) + coverCornerSquare( TRUE ); + if ( autoUpdate() ) + sbDirty = sbDirty | verMask; + } else { + tFlags &= ~Tbl_vScrollBar; + if ( !vScrollBar ) + return; + coverCornerSquare( FALSE ); + bool hideScrollBar = autoUpdate() && vScrollBar->isVisible(); + if ( hideScrollBar ) + vScrollBar->hide(); + if ( update ) + updateScrollBars( horMask ); + else + sbDirty = sbDirty | horMask; + if ( hideScrollBar && isVisible() ) + repaint( vScrollBar->x(), vScrollBar->y(), + vScrollBar->width(), height() - vScrollBar->y() ); + } + if ( update ) + updateFrameSize(); +} + + + + +int QtTableView::findRawRow( int yPos, int *cellMaxY, int *cellMinY, + bool goOutsideView ) const +{ + int r = -1; + if ( nRows == 0 ) + return r; + if ( goOutsideView || yPos >= minViewY() && yPos <= maxViewY() ) { + if ( yPos < minViewY() ) { +#if defined(QT_CHECK_RANGE) + qWarning( "QtTableView::findRawRow: (%s) internal error: " + "yPos < minViewY() && goOutsideView " + "not supported. (%d,%d)", + name( "unnamed" ), yPos, yOffs ); +#endif + return -1; + } + if ( cellH ) { // uniform cell height + r = (yPos - minViewY() + yCellDelta)/cellH; // cell offs from top + if ( cellMaxY ) + *cellMaxY = (r + 1)*cellH + minViewY() - yCellDelta - 1; + if ( cellMinY ) + *cellMinY = r*cellH + minViewY() - yCellDelta; + r += yCellOffs; // absolute cell index + } else { // variable cell height + QtTableView *tw = (QtTableView *)this; + r = yCellOffs; + int h = minViewY() - yCellDelta; //##arnt3 + int oldH = h; + Q_ASSERT( r < nRows ); + while ( r < nRows ) { + oldH = h; + h += tw->cellHeight( r ); // Start of next cell + if ( yPos < h ) + break; + r++; + } + if ( cellMaxY ) + *cellMaxY = h - 1; + if ( cellMinY ) + *cellMinY = oldH; + } + } + return r; + +} + + +int QtTableView::findRawCol( int xPos, int *cellMaxX, int *cellMinX , + bool goOutsideView ) const +{ + int c = -1; + if ( nCols == 0 ) + return c; + if ( goOutsideView || xPos >= minViewX() && xPos <= maxViewX() ) { + if ( xPos < minViewX() ) { +#if defined(QT_CHECK_RANGE) + qWarning( "QtTableView::findRawCol: (%s) internal error: " + "xPos < minViewX() && goOutsideView " + "not supported. (%d,%d)", + name( "unnamed" ), xPos, xOffs ); +#endif + return -1; + } + if ( cellW ) { // uniform cell width + c = (xPos - minViewX() + xCellDelta)/cellW; //cell offs from left + if ( cellMaxX ) + *cellMaxX = (c + 1)*cellW + minViewX() - xCellDelta - 1; + if ( cellMinX ) + *cellMinX = c*cellW + minViewX() - xCellDelta; + c += xCellOffs; // absolute cell index + } else { // variable cell width + QtTableView *tw = (QtTableView *)this; + c = xCellOffs; + int w = minViewX() - xCellDelta; //##arnt3 + int oldW = w; + Q_ASSERT( c < nCols ); + while ( c < nCols ) { + oldW = w; + w += tw->cellWidth( c ); // Start of next cell + if ( xPos < w ) + break; + c++; + } + if ( cellMaxX ) + *cellMaxX = w - 1; + if ( cellMinX ) + *cellMinX = oldW; + } + } + return c; +} + + +/*! + Returns the index of the row at position \a yPos, where \a yPos is in + \e widget coordinates. Returns -1 if \a yPos is outside the valid + range. + + \sa findCol(), rowYPos() +*/ + +int QtTableView::findRow( int yPos ) const +{ + int cellMaxY; + int row = findRawRow( yPos, &cellMaxY ); + if ( testTableFlags(Tbl_cutCellsV) && cellMaxY > maxViewY() ) + row = - 1; // cell cut by bottom margin + if ( row >= nRows ) + row = -1; + return row; +} + + +/*! + Returns the index of the column at position \a xPos, where \a xPos is + in \e widget coordinates. Returns -1 if \a xPos is outside the valid + range. + + \sa findRow(), colXPos() +*/ + +int QtTableView::findCol( int xPos ) const +{ + int cellMaxX; + int col = findRawCol( xPos, &cellMaxX ); + if ( testTableFlags(Tbl_cutCellsH) && cellMaxX > maxViewX() ) + col = - 1; // cell cut by right margin + if ( col >= nCols ) + col = -1; + return col; +} + + +/*! + Computes the position in the widget of row \a row. + + Returns TRUE and stores the result in \a *yPos (in \e widget + coordinates) if the row is visible. Returns FALSE and does not modify + \a *yPos if \a row is invisible or invalid. + + \sa colXPos(), findRow() +*/ + +bool QtTableView::rowYPos( int row, int *yPos ) const +{ + int y; + if ( row >= yCellOffs ) { + if ( cellH ) { + int lastVisible = lastRowVisible(); + if ( row > lastVisible || lastVisible == -1 ) + return FALSE; + y = (row - yCellOffs)*cellH + minViewY() - yCellDelta; + } else { + //##arnt3 + y = minViewY() - yCellDelta; // y of leftmost cell in view + int r = yCellOffs; + QtTableView *tw = (QtTableView *)this; + int maxY = maxViewY(); + while ( r < row && y <= maxY ) + y += tw->cellHeight( r++ ); + if ( y > maxY ) + return FALSE; + + } + } else { + return FALSE; + } + if ( yPos ) + *yPos = y; + return TRUE; +} + + +/*! + Computes the position in the widget of column \a col. + + Returns TRUE and stores the result in \a *xPos (in \e widget + coordinates) if the column is visible. Returns FALSE and does not + modify \a *xPos if \a col is invisible or invalid. + + \sa rowYPos(), findCol() +*/ + +bool QtTableView::colXPos( int col, int *xPos ) const +{ + int x; + if ( col >= xCellOffs ) { + if ( cellW ) { + int lastVisible = lastColVisible(); + if ( col > lastVisible || lastVisible == -1 ) + return FALSE; + x = (col - xCellOffs)*cellW + minViewX() - xCellDelta; + } else { + //##arnt3 + x = minViewX() - xCellDelta; // x of uppermost cell in view + int c = xCellOffs; + QtTableView *tw = (QtTableView *)this; + int maxX = maxViewX(); + while ( c < col && x <= maxX ) + x += tw->cellWidth( c++ ); + if ( x > maxX ) + return FALSE; + } + } else { + return FALSE; + } + if ( xPos ) + *xPos = x; + return TRUE; +} + + +/*! + Moves the visible area of the table right by \a xPixels and + down by \a yPixels pixels. Both may be negative. + + \warning You might find that QScrollView offers a higher-level of + functionality than using QtTableView and this function. + + This function is \e not the same as QWidget::scroll(); in particular, + the signs of \a xPixels and \a yPixels have the reverse semantics. + + \sa setXOffset(), setYOffset(), setOffset(), setTopCell(), + setLeftCell() +*/ + +void QtTableView::scroll( int xPixels, int yPixels ) +{ + QWidget::scroll( -xPixels, -yPixels, contentsRect() ); +} + + +/*! + Returns the leftmost pixel of the table view in \e view + coordinates. This excludes the frame and any header. + + \sa maxViewY(), viewWidth(), contentsRect() +*/ + +int QtTableView::minViewX() const +{ + return frameWidth(); +} + + +/*! + Returns the top pixel of the table view in \e view + coordinates. This excludes the frame and any header. + + \sa maxViewX(), viewHeight(), contentsRect() +*/ + +int QtTableView::minViewY() const +{ + return frameWidth(); +} + + +/*! + Returns the rightmost pixel of the table view in \e view + coordinates. This excludes the frame and any scroll bar, but + includes blank pixels to the right of the visible table data. + + \sa maxViewY(), viewWidth(), contentsRect() +*/ + +int QtTableView::maxViewX() const +{ + return width() - 1 - frameWidth() + - (tFlags & Tbl_vScrollBar ? VSBEXT + : 0); +} + + +/*! + Returns the bottom pixel of the table view in \e view + coordinates. This excludes the frame and any scroll bar, but + includes blank pixels below the visible table data. + + \sa maxViewX(), viewHeight(), contentsRect() +*/ + +int QtTableView::maxViewY() const +{ + return height() - 1 - frameWidth() + - (tFlags & Tbl_hScrollBar ? HSBEXT + : 0); +} + + +/*! + Returns the width of the table view, as such, in \e view + coordinates. This does not include any header, scroll bar or frame, + but it does include background pixels to the right of the table data. + + \sa minViewX() maxViewX(), viewHeight(), contentsRect() viewRect() +*/ + +int QtTableView::viewWidth() const +{ + return maxViewX() - minViewX() + 1; +} + + +/*! + Returns the height of the table view, as such, in \e view + coordinates. This does not include any header, scroll bar or frame, + but it does include background pixels below the table data. + + \sa minViewY() maxViewY() viewWidth() contentsRect() viewRect() +*/ + +int QtTableView::viewHeight() const +{ + return maxViewY() - minViewY() + 1; +} + + +void QtTableView::doAutoScrollBars() +{ + int viewW = width() - frameWidth() - minViewX(); + int viewH = height() - frameWidth() - minViewY(); + bool vScrollOn = testTableFlags(Tbl_vScrollBar); + bool hScrollOn = testTableFlags(Tbl_hScrollBar); + int w = 0; + int h = 0; + int i; + + if ( testTableFlags(Tbl_autoHScrollBar) ) { + if ( cellW ) { + w = cellW*nCols; + } else { + i = 0; + while ( i < nCols && w <= viewW ) + w += cellWidth( i++ ); + } + if ( w > viewW ) + hScrollOn = TRUE; + else + hScrollOn = FALSE; + } + + if ( testTableFlags(Tbl_autoVScrollBar) ) { + if ( cellH ) { + h = cellH*nRows; + } else { + i = 0; + while ( i < nRows && h <= viewH ) + h += cellHeight( i++ ); + } + + if ( h > viewH ) + vScrollOn = TRUE; + else + vScrollOn = FALSE; + } + + if ( testTableFlags(Tbl_autoHScrollBar) && vScrollOn && !hScrollOn ) + if ( w > viewW - VSBEXT ) + hScrollOn = TRUE; + + if ( testTableFlags(Tbl_autoVScrollBar) && hScrollOn && !vScrollOn ) + if ( h > viewH - HSBEXT ) + vScrollOn = TRUE; + + setHorScrollBar( hScrollOn, FALSE ); + setVerScrollBar( vScrollOn, FALSE ); + updateFrameSize(); +} + + +/*! + \fn void QtTableView::updateScrollBars() + + Updates the scroll bars' contents and presence to match the table's + state. Generally, you should not need to call this. + + \sa setTableFlags() +*/ + +/*! + Updates the scroll bars' contents and presence to match the table's + state \c or \a f. + + \sa setTableFlags() +*/ + +void QtTableView::updateScrollBars( uint f ) +{ + sbDirty = sbDirty | f; + if ( inSbUpdate ) + return; + inSbUpdate = TRUE; + + if ( testTableFlags(Tbl_autoHScrollBar) && (sbDirty & horRange) || + testTableFlags(Tbl_autoVScrollBar) && (sbDirty & verRange) ) + // if range change and auto + doAutoScrollBars(); // turn scroll bars on/off if needed + + if ( !autoUpdate() ) { + inSbUpdate = FALSE; + return; + } + if ( yOffset() > 0 && testTableFlags( Tbl_autoVScrollBar ) && + !testTableFlags( Tbl_vScrollBar ) ) { + setYOffset( 0 ); + } + if ( xOffset() > 0 && testTableFlags( Tbl_autoHScrollBar ) && + !testTableFlags( Tbl_hScrollBar ) ) { + setXOffset( 0 ); + } + if ( !isVisible() ) { + inSbUpdate = FALSE; + return; + } + + if ( testTableFlags(Tbl_hScrollBar) && (sbDirty & horMask) != 0 ) { + if ( sbDirty & horGeometry ) + hScrollBar->setGeometry( 0,height() - HSBEXT, + viewWidth() + frameWidth()*2, + HSBEXT); + + if ( sbDirty & horSteps ) { + if ( cellW ) + hScrollBar->setSteps( QMIN(cellW,viewWidth()/2), viewWidth() ); + else + hScrollBar->setSteps( 16, viewWidth() ); + } + + if ( sbDirty & horRange ) + hScrollBar->setRange( 0, maxXOffset() ); + + if ( sbDirty & horValue ) + hScrollBar->setValue( xOffs ); + + // show scrollbar only when it has a sane geometry + if ( !hScrollBar->isVisible() ) + hScrollBar->show(); + } + + if ( testTableFlags(Tbl_vScrollBar) && (sbDirty & verMask) != 0 ) { + if ( sbDirty & verGeometry ) + vScrollBar->setGeometry( width() - VSBEXT, 0, + VSBEXT, + viewHeight() + frameWidth()*2 ); + + if ( sbDirty & verSteps ) { + if ( cellH ) + vScrollBar->setSteps( QMIN(cellH,viewHeight()/2), viewHeight() ); + else + vScrollBar->setSteps( 16, viewHeight() ); // fttb! ### + } + + if ( sbDirty & verRange ) + vScrollBar->setRange( 0, maxYOffset() ); + + if ( sbDirty & verValue ) + vScrollBar->setValue( yOffs ); + + // show scrollbar only when it has a sane geometry + if ( !vScrollBar->isVisible() ) + vScrollBar->show(); + } + if ( coveringCornerSquare && + ( (sbDirty & verGeometry ) || (sbDirty & horGeometry)) ) + cornerSquare->move( maxViewX() + frameWidth() + 1, + maxViewY() + frameWidth() + 1 ); + + sbDirty = 0; + inSbUpdate = FALSE; +} + + +void QtTableView::updateFrameSize() +{ + int rw = width() - ( testTableFlags(Tbl_vScrollBar) ? + VSBEXT : 0 ); + int rh = height() - ( testTableFlags(Tbl_hScrollBar) ? + HSBEXT : 0 ); + if ( rw < 0 ) + rw = 0; + if ( rh < 0 ) + rh = 0; + + if ( autoUpdate() ) { + int fh = frameRect().height(); + int fw = frameRect().width(); + setFrameRect( QRect(0,0,rw,rh) ); + + if ( rw != fw ) + update( QMIN(fw,rw) - frameWidth() - 2, 0, frameWidth()+4, rh ); + if ( rh != fh ) + update( 0, QMIN(fh,rh) - frameWidth() - 2, rw, frameWidth()+4 ); + } +} + + +/*! + Returns the maximum horizontal offset within the table of the + view's left edge in \e table coordinates. + + This is used mainly to set the horizontal scroll bar's range. + + \sa maxColOffset(), maxYOffset(), totalWidth() +*/ + +int QtTableView::maxXOffset() +{ + int tw = totalWidth(); + int maxOffs; + if ( testTableFlags(Tbl_scrollLastHCell) ) { + if ( nCols != 1) + maxOffs = tw - ( cellW ? cellW : cellWidth( nCols - 1 ) ); + else + maxOffs = tw - viewWidth(); + } else { + if ( testTableFlags(Tbl_snapToHGrid) ) { + if ( cellW ) { + maxOffs = tw - (viewWidth()/cellW)*cellW; + } else { + int goal = tw - viewWidth(); + int pos = tw; + int nextCol = nCols - 1; + int nextCellWidth = cellWidth( nextCol ); + while( nextCol > 0 && pos > goal + nextCellWidth ) { + pos -= nextCellWidth; + nextCellWidth = cellWidth( --nextCol ); + } + if ( goal + nextCellWidth == pos ) + maxOffs = goal; + else if ( goal < pos ) + maxOffs = pos; + else + maxOffs = 0; + } + } else { + maxOffs = tw - viewWidth(); + } + } + return maxOffs > 0 ? maxOffs : 0; +} + + +/*! + Returns the maximum vertical offset within the table of the + view's top edge in \e table coordinates. + + This is used mainly to set the vertical scroll bar's range. + + \sa maxRowOffset(), maxXOffset(), totalHeight() +*/ + +int QtTableView::maxYOffset() +{ + int th = totalHeight(); + int maxOffs; + if ( testTableFlags(Tbl_scrollLastVCell) ) { + if ( nRows != 1) + maxOffs = th - ( cellH ? cellH : cellHeight( nRows - 1 ) ); + else + maxOffs = th - viewHeight(); + } else { + if ( testTableFlags(Tbl_snapToVGrid) ) { + if ( cellH ) { + maxOffs = th - (viewHeight()/cellH)*cellH; + } else { + int goal = th - viewHeight(); + int pos = th; + int nextRow = nRows - 1; + int nextCellHeight = cellHeight( nextRow ); + while( nextRow > 0 && pos > goal + nextCellHeight ) { + pos -= nextCellHeight; + nextCellHeight = cellHeight( --nextRow ); + } + if ( goal + nextCellHeight == pos ) + maxOffs = goal; + else if ( goal < pos ) + maxOffs = pos; + else + maxOffs = 0; + } + } else { + maxOffs = th - viewHeight(); + } + } + return maxOffs > 0 ? maxOffs : 0; +} + + +/*! + Returns the index of the last column, which may be at the left edge + of the view. + + Depending on the \link setTableFlags() Tbl_scrollLastHCell\endlink flag, + this may or may not be the last column. + + \sa maxXOffset(), maxRowOffset() +*/ + +int QtTableView::maxColOffset() +{ + int mx = maxXOffset(); + if ( cellW ) + return mx/cellW; + else { + int xcd=0, col=0; + while ( col < nCols && mx > (xcd=cellWidth(col)) ) { + mx -= xcd; + col++; + } + return col; + } +} + + +/*! + Returns the index of the last row, which may be at the top edge of + the view. + + Depending on the \link setTableFlags() Tbl_scrollLastVCell\endlink flag, + this may or may not be the last row. + + \sa maxYOffset(), maxColOffset() +*/ + +int QtTableView::maxRowOffset() +{ + int my = maxYOffset(); + if ( cellH ) + return my/cellH; + else { + int ycd=0, row=0; + while ( row < nRows && my > (ycd=cellHeight(row)) ) { + my -= ycd; + row++; + } + return row; + } +} + + +void QtTableView::showOrHideScrollBars() +{ + if ( !autoUpdate() ) + return; + if ( vScrollBar ) { + if ( testTableFlags(Tbl_vScrollBar) ) { + if ( !vScrollBar->isVisible() ) + sbDirty = sbDirty | verMask; + } else { + if ( vScrollBar->isVisible() ) + vScrollBar->hide(); + } + } + if ( hScrollBar ) { + if ( testTableFlags(Tbl_hScrollBar) ) { + if ( !hScrollBar->isVisible() ) + sbDirty = sbDirty | horMask; + } else { + if ( hScrollBar->isVisible() ) + hScrollBar->hide(); + } + } + if ( cornerSquare ) { + if ( testTableFlags(Tbl_hScrollBar) && + testTableFlags(Tbl_vScrollBar) ) { + if ( !cornerSquare->isVisible() ) + cornerSquare->show(); + } else { + if ( cornerSquare->isVisible() ) + cornerSquare->hide(); + } + } +} + + +/*! + Updates the scroll bars and internal state. + + Call this function when the table view's total size is changed; + typically because the result of cellHeight() or cellWidth() have changed. + + This function does not repaint the widget. +*/ + +void QtTableView::updateTableSize() +{ + bool updateOn = autoUpdate(); + setAutoUpdate( FALSE ); + int xofs = xOffset(); + xOffs++; //so that setOffset will not return immediately + setOffset(xofs,yOffset(),FALSE); //to calculate internal state correctly + setAutoUpdate(updateOn); + + updateScrollBars( horSteps | horRange | + verSteps | verRange ); + showOrHideScrollBars(); +} + + +#include "qttableview.moc" diff --git a/cervisia/qttableview.h b/cervisia/qttableview.h new file mode 100644 index 00000000..6f8b8397 --- /dev/null +++ b/cervisia/qttableview.h @@ -0,0 +1,252 @@ +/********************************************************************** +** $Id$ +** +** Definition of QtTableView class +** +** Created : 941115 +** +** Copyright (C) 1992-2000 Trolltech AS. All rights reserved. +** +** This file contains a class moved out of the Qt GUI Toolkit API. It +** may be used, distributed and modified without limitation. +** +**********************************************************************/ + +#ifndef QTTABLEVIEW_H +#define QTTABLEVIEW_H + +#ifndef QT_H +#include "qframe.h" +#endif // QT_H + +#ifndef QT_NO_QTTABLEVIEW + +class QScrollBar; +class QCornerSquare; + + +class QtTableView : public QFrame +{ + Q_OBJECT +public: + virtual void setBackgroundColor( const QColor & ); + virtual void setPalette( const QPalette & ); + void show(); + + void repaint( bool erase=TRUE ); + void repaint( int x, int y, int w, int h, bool erase=TRUE ); + void repaint( const QRect &, bool erase=TRUE ); + +protected: + QtTableView( QWidget *parent=0, const char *name=0, WFlags f=0 ); + ~QtTableView(); + + int numRows() const; + virtual void setNumRows( int ); + int numCols() const; + virtual void setNumCols( int ); + + int topCell() const; + virtual void setTopCell( int row ); + int leftCell() const; + virtual void setLeftCell( int col ); + virtual void setTopLeftCell( int row, int col ); + + int xOffset() const; + virtual void setXOffset( int ); + int yOffset() const; + virtual void setYOffset( int ); + virtual void setOffset( int x, int y, bool updateScrBars = TRUE ); + + virtual int cellWidth( int col ); + virtual int cellHeight( int row ); + int cellWidth() const; + int cellHeight() const; + virtual void setCellWidth( int ); + virtual void setCellHeight( int ); + + virtual int totalWidth(); + virtual int totalHeight(); + + uint tableFlags() const; + bool testTableFlags( uint f ) const; + virtual void setTableFlags( uint f ); + void clearTableFlags( uint f = ~0 ); + + bool autoUpdate() const; + virtual void setAutoUpdate( bool ); + + void updateCell( int row, int column, bool erase=TRUE ); + + QRect cellUpdateRect() const; + QRect viewRect() const; + + int lastRowVisible() const; + int lastColVisible() const; + + bool rowIsVisible( int row ) const; + bool colIsVisible( int col ) const; + + QScrollBar *verticalScrollBar() const; + QScrollBar *horizontalScrollBar() const; + +private slots: + void horSbValue( int ); + void horSbSliding( int ); + void horSbSlidingDone(); + void verSbValue( int ); + void verSbSliding( int ); + void verSbSlidingDone(); + +protected: + virtual void paintCell( QPainter *, int row, int col ) = 0; + virtual void setupPainter( QPainter * ); + + void paintEvent( QPaintEvent * ); + void resizeEvent( QResizeEvent * ); + virtual void wheelEvent( QWheelEvent *e ); + + int findRow( int yPos ) const; + int findCol( int xPos ) const; + + bool rowYPos( int row, int *yPos ) const; + bool colXPos( int col, int *xPos ) const; + + int maxXOffset(); + int maxYOffset(); + int maxColOffset(); + int maxRowOffset(); + + int minViewX() const; + int minViewY() const; + int maxViewX() const; + int maxViewY() const; + int viewWidth() const; + int viewHeight() const; + + void scroll( int xPixels, int yPixels ); + void updateScrollBars(); + void updateTableSize(); + +private: + void coverCornerSquare( bool ); + void snapToGrid( bool horizontal, bool vertical ); + virtual void setHorScrollBar( bool on, bool update = TRUE ); + virtual void setVerScrollBar( bool on, bool update = TRUE ); + void updateView(); + int findRawRow( int yPos, int *cellMaxY, int *cellMinY = 0, + bool goOutsideView = FALSE ) const; + int findRawCol( int xPos, int *cellMaxX, int *cellMinX = 0, + bool goOutsideView = FALSE ) const; + int maxColsVisible() const; + + void updateScrollBars( uint ); + void updateFrameSize(); + + void doAutoScrollBars(); + void showOrHideScrollBars(); + + int nRows; + int nCols; + int xOffs, yOffs; + int xCellOffs, yCellOffs; + short xCellDelta, yCellDelta; + short cellH, cellW; + + uint eraseInPaint : 1; + uint verSliding : 1; + uint verSnappingOff : 1; + uint horSliding : 1; + uint horSnappingOff : 1; + uint coveringCornerSquare : 1; + uint sbDirty : 8; + uint inSbUpdate : 1; + + uint tFlags; + QRect cellUpdateR; + + QScrollBar *vScrollBar; + QScrollBar *hScrollBar; + QCornerSquare *cornerSquare; + +private: // Disabled copy constructor and operator= +#if defined(Q_DISABLE_COPY) + QtTableView( const QtTableView & ); + QtTableView &operator=( const QtTableView & ); +#endif +}; + + +const uint Tbl_vScrollBar = 0x00000001; +const uint Tbl_hScrollBar = 0x00000002; +const uint Tbl_autoVScrollBar = 0x00000004; +const uint Tbl_autoHScrollBar = 0x00000008; +const uint Tbl_autoScrollBars = 0x0000000C; + +const uint Tbl_clipCellPainting = 0x00000100; +const uint Tbl_cutCellsV = 0x00000200; +const uint Tbl_cutCellsH = 0x00000400; +const uint Tbl_cutCells = 0x00000600; + +const uint Tbl_scrollLastHCell = 0x00000800; +const uint Tbl_scrollLastVCell = 0x00001000; +const uint Tbl_scrollLastCell = 0x00001800; + +const uint Tbl_smoothHScrolling = 0x00002000; +const uint Tbl_smoothVScrolling = 0x00004000; +const uint Tbl_smoothScrolling = 0x00006000; + +const uint Tbl_snapToHGrid = 0x00008000; +const uint Tbl_snapToVGrid = 0x00010000; +const uint Tbl_snapToGrid = 0x00018000; + + +inline int QtTableView::numRows() const +{ return nRows; } + +inline int QtTableView::numCols() const +{ return nCols; } + +inline int QtTableView::topCell() const +{ return yCellOffs; } + +inline int QtTableView::leftCell() const +{ return xCellOffs; } + +inline int QtTableView::xOffset() const +{ return xOffs; } + +inline int QtTableView::yOffset() const +{ return yOffs; } + +inline int QtTableView::cellHeight() const +{ return cellH; } + +inline int QtTableView::cellWidth() const +{ return cellW; } + +inline uint QtTableView::tableFlags() const +{ return tFlags; } + +inline bool QtTableView::testTableFlags( uint f ) const +{ return (tFlags & f) != 0; } + +inline QRect QtTableView::cellUpdateRect() const +{ return cellUpdateR; } + +inline bool QtTableView::autoUpdate() const +{ return isUpdatesEnabled(); } + +inline void QtTableView::repaint( bool erase ) +{ repaint( 0, 0, width(), height(), erase ); } + +inline void QtTableView::repaint( const QRect &r, bool erase ) +{ repaint( r.x(), r.y(), r.width(), r.height(), erase ); } + +inline void QtTableView::updateScrollBars() +{ updateScrollBars( 0 ); } + + +#endif // QT_NO_QTTABLEVIEW + +#endif // QTTABLEVIEW_H diff --git a/cervisia/repositories.cpp b/cervisia/repositories.cpp new file mode 100644 index 00000000..4c517116 --- /dev/null +++ b/cervisia/repositories.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 1999-2001 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include <stdlib.h> +#include <qfile.h> +#include <qdir.h> +#include <qtextstream.h> +#include <kapplication.h> +#include <kconfig.h> + +#include "repositories.h" +#include "cervisiapart.h" + + +static QString fileNameCvs() +{ + return QDir::homeDirPath() + "/.cvspass"; +} + + +static QString fileNameCvsnt() +{ + return QDir::homeDirPath() + "/.cvs/cvspass"; +} + + +// old .cvspass format: +// user@host:/path Acleartext_password +// +// new .cvspass format (since cvs 1.11.1): +// /1 user@host:port/path Aencoded_password +// +static QStringList readCvsPassFile() +{ + QStringList list; + + QFile f(fileNameCvs()); + if (f.open(IO_ReadOnly)) + { + QTextStream stream(&f); + while (!stream.eof()) + { + int pos; + QString line = stream.readLine(); + if ( (pos = line.find(' ')) != -1) + { + if (line[0] != '/') // old format + list.append(line.left(pos)); + else // new format + list.append(line.section(' ', 1, 1)); + } + } + } + + return list; +} + + +// .cvs/cvspass format +// user@host:port/path=Aencoded_password +// +static QStringList readCvsntPassFile() +{ + QStringList list; + + QFile file(fileNameCvsnt()); + if (file.open(IO_ReadOnly)) + { + QTextStream stream(&file); + while (!stream.atEnd()) + { + const QString line(stream.readLine()); + + const int pos(line.find("=A")); + if (pos >= 0) + list.append(line.left(pos)); + } + } + + return list; +} + + +QStringList Repositories::readCvsPassFile() +{ + return (QFileInfo(fileNameCvs()).lastModified() + < QFileInfo(fileNameCvsnt()).lastModified()) + ? readCvsntPassFile() + : ::readCvsPassFile(); +} + + +QStringList Repositories::readConfigFile() +{ + QStringList list; + + KConfig *config = CervisiaPart::config(); + config->setGroup("Repositories"); + list = config->readListEntry("Repos"); + + // Some people actually use CVSROOT, so we add it here + char *env; + if ( (env = ::getenv("CVSROOT")) != 0 && !list.contains(env)) + list.append(env); + + return list; +} + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/repositories.h b/cervisia/repositories.h new file mode 100644 index 00000000..28d7a469 --- /dev/null +++ b/cervisia/repositories.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 1999-2001 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef REPOSITORIES_H +#define REPOSITORIES_H + + +class Repositories +{ +public: + static QStringList readCvsPassFile(); + static QStringList readConfigFile(); +}; + + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/repositorydlg.cpp b/cervisia/repositorydlg.cpp new file mode 100644 index 00000000..d0568398 --- /dev/null +++ b/cervisia/repositorydlg.cpp @@ -0,0 +1,504 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2002-2006 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "repositorydlg.h" + +#include <qlayout.h> +#include <qpushbutton.h> +#include <kbuttonbox.h> +#include <kconfig.h> +#include <klistview.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kdebug.h> + +#include "addrepositorydlg.h" +#include "cvsservice_stub.h" +#include "misc.h" +#include "progressdlg.h" +#include "repositories.h" + + +class RepositoryListItem : public KListViewItem +{ +public: + RepositoryListItem(KListView* parent, const QString& repo, bool loggedin); + + void setRsh(const QString& rsh); + void setServer(const QString& server) { m_server = server; } + void setCompression(int compression); + void setIsLoggedIn(bool isLoggedIn); + void setRetrieveCvsignore(bool retrieve) { m_retrieveCvsignore = retrieve; } + + QString repository() const + { + return text(0); + } + QString rsh() const + { + QString str = text(1); + return (str.startsWith("ext (") ? str.mid(5, str.length()-6) + : QString::null); + } + QString server() const { return m_server; } + int compression() const + { + bool ok; + int n = text(2).toInt(&ok); + return ok ? n : -1; + } + bool isLoggedIn() const { return m_isLoggedIn; } + bool retrieveCvsignore() const { return m_retrieveCvsignore; } + +private: + void changeLoginStatusColumn(); + +private: + QString m_server; + bool m_isLoggedIn; + bool m_retrieveCvsignore; +}; + + +static bool LoginNeeded(const QString& repository) +{ + return repository.startsWith(":pserver:") || + repository.startsWith(":sspi:"); +} + + +RepositoryListItem::RepositoryListItem(KListView* parent, const QString& repo, + bool loggedin) + : KListViewItem(parent) + , m_isLoggedIn(loggedin) +{ + setText(0, repo); + + changeLoginStatusColumn(); +} + + +void RepositoryListItem::setRsh(const QString& rsh) +{ + QString repo = repository(); + QString method; + + if( repo.startsWith(":pserver:") ) + method = "pserver"; + else if( repo.startsWith(":sspi:") ) + method = "sspi"; + else if( repo.contains(':') ) + { + method = "ext"; + if( !rsh.isEmpty() ) + { + method += " ("; + method += rsh; + method += ")"; + } + } + else + method = "local"; + + setText(1, method); +} + + +void RepositoryListItem::setCompression(int compression) +{ + QString compressionStr = (compression >= 0) ? QString::number(compression) + : i18n("Default"); + + setText(2, compressionStr); +} + + +void RepositoryListItem::setIsLoggedIn(bool isLoggedIn) +{ + m_isLoggedIn = isLoggedIn; + + changeLoginStatusColumn(); +} + + +void RepositoryListItem::changeLoginStatusColumn() +{ + QString loginStatus; + + if( LoginNeeded(repository()) ) + loginStatus = m_isLoggedIn ? i18n("Logged in") : i18n("Not logged in"); + else + loginStatus = i18n("No login required"); + + setText(3, loginStatus); +} + + +RepositoryDialog::RepositoryDialog(KConfig& cfg, CvsService_stub* cvsService, + QWidget* parent, const char* name) + : KDialogBase(parent, name, true, i18n("Configure Access to Repositories"), + Ok | Cancel | Help, Ok, true) + , m_partConfig(cfg) + , m_cvsService(cvsService) +{ + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout* hbox = new QHBoxLayout(mainWidget, 0, spacingHint()); + + m_repoList = new KListView(mainWidget); + hbox->addWidget(m_repoList, 10); + m_repoList->setMinimumWidth(fontMetrics().width('0') * 60); + m_repoList->setAllColumnsShowFocus(true); + m_repoList->addColumn(i18n("Repository")); + m_repoList->addColumn(i18n("Method")); + m_repoList->addColumn(i18n("Compression")); + m_repoList->addColumn(i18n("Status")); + m_repoList->setFocus(); + + connect(m_repoList, SIGNAL(doubleClicked(QListViewItem*)), + this, SLOT(slotDoubleClicked(QListViewItem*))); + connect(m_repoList, SIGNAL(selectionChanged()), + this, SLOT(slotSelectionChanged())); + + KButtonBox* actionbox = new KButtonBox(mainWidget, KButtonBox::Vertical); + QPushButton* addbutton = actionbox->addButton(i18n("&Add...")); + m_modifyButton = actionbox->addButton(i18n("&Modify...")); + m_removeButton = actionbox->addButton(i18n("&Remove")); + actionbox->addStretch(); + m_loginButton = actionbox->addButton(i18n("Login...")); + m_logoutButton = actionbox->addButton(i18n("Logout")); + actionbox->addStretch(); + actionbox->layout(); + hbox->addWidget(actionbox, 0); + + m_loginButton->setEnabled(false); + m_logoutButton->setEnabled(false); + + connect( addbutton, SIGNAL(clicked()), + this, SLOT(slotAddClicked()) ); + connect( m_modifyButton, SIGNAL(clicked()), + this, SLOT(slotModifyClicked()) ); + connect( m_removeButton, SIGNAL(clicked()), + this, SLOT(slotRemoveClicked()) ); + connect( m_loginButton, SIGNAL(clicked()), + this, SLOT(slotLoginClicked()) ); + connect( m_logoutButton, SIGNAL(clicked()), + this, SLOT(slotLogoutClicked()) ); + + // open cvs DCOP service configuration file + m_serviceConfig = new KConfig("cvsservicerc"); + + readCvsPassFile(); + readConfigFile(); + + if (QListViewItem* item = m_repoList->firstChild()) + { + m_repoList->setCurrentItem(item); + m_repoList->setSelected(item, true); + } + else + { + // we have no item so disable modify and remove button + slotSelectionChanged(); + } + + setHelp("accessing-repository"); + + setWFlags(Qt::WDestructiveClose | getWFlags()); + + QSize size = configDialogSize(m_partConfig, "RepositoryDialog"); + resize(size); + + // without this restoreLayout() can't change the column widths + for (int i = 0; i < m_repoList->columns(); ++i) + m_repoList->setColumnWidthMode(i, QListView::Manual); + + m_repoList->restoreLayout(&m_partConfig, QString::fromLatin1("RepositoryListView")); +} + + +RepositoryDialog::~RepositoryDialog() +{ + saveDialogSize(m_partConfig, "RepositoryDialog"); + + m_repoList->saveLayout(&m_partConfig, QString::fromLatin1("RepositoryListView")); + + delete m_serviceConfig; +} + + +void RepositoryDialog::readCvsPassFile() +{ + QStringList list = Repositories::readCvsPassFile(); + QStringList::ConstIterator it; + for( it = list.begin(); it != list.end(); ++it ) + (void) new RepositoryListItem(m_repoList, (*it), true); +} + + +void RepositoryDialog::readConfigFile() +{ + QStringList list = Repositories::readConfigFile(); + + // Sort out all list elements which are already in the list view + QListViewItem* item = m_repoList->firstChild(); + for( ; item; item = item->nextSibling() ) + list.remove(item->text(0)); + + QStringList::ConstIterator it; + for( it = list.begin(); it != list.end(); ++it ) + new RepositoryListItem(m_repoList, *it, false); + + // Now look for the used methods + item = m_repoList->firstChild(); + for( ; item; item = item->nextSibling() ) + { + RepositoryListItem* ritem = static_cast<RepositoryListItem*>(item); + + // read entries from cvs DCOP service configuration + m_serviceConfig->setGroup(QString::fromLatin1("Repository-") + + ritem->repository()); + + QString rsh = m_serviceConfig->readEntry("rsh", QString()); + QString server = m_serviceConfig->readEntry("cvs_server", QString()); + int compression = m_serviceConfig->readNumEntry("Compression", -1); + bool retrieveFile = m_serviceConfig->readBoolEntry("RetrieveCvsignore", + false); + + ritem->setRsh(rsh); + ritem->setServer(server); + ritem->setCompression(compression); + ritem->setRetrieveCvsignore(retrieveFile); + } +} + + +void RepositoryDialog::slotOk() +{ + // Make list of repositories + QListViewItem* item; + QStringList list; + for( item = m_repoList->firstChild(); item; item = item->nextSibling() ) + list.append(item->text(0)); + + m_partConfig.setGroup("Repositories"); + m_partConfig.writeEntry("Repos", list); + + for( item = m_repoList->firstChild(); item; item = item->nextSibling() ) + { + RepositoryListItem* ritem = static_cast<RepositoryListItem*>(item); + + // write entries to cvs DCOP service configuration + writeRepositoryData(ritem); + } + + // write to disk so other services can reparse the configuration + m_serviceConfig->sync(); + + KDialogBase::slotOk(); +} + + +void RepositoryDialog::slotAddClicked() +{ + AddRepositoryDialog dlg(m_partConfig, QString::null, this); + // default compression level + dlg.setCompression(-1); + if( dlg.exec() ) + { + QString repo = Cervisia::NormalizeRepository(dlg.repository()); + QString rsh = dlg.rsh(); + QString server = dlg.server(); + int compression = dlg.compression(); + bool retrieveFile = dlg.retrieveCvsignoreFile(); + + QListViewItem* item = m_repoList->firstChild(); + for( ; item; item = item->nextSibling() ) + if( item->text(0) == repo ) + { + KMessageBox::information(this, i18n("This repository is already known.")); + return; + } + + RepositoryListItem* ritem = new RepositoryListItem(m_repoList, repo, false); + ritem->setRsh(rsh); + ritem->setCompression(compression); + ritem->setRetrieveCvsignore(retrieveFile); + + // write entries to cvs DCOP service configuration + writeRepositoryData(ritem); + + // write to disk so other services can reparse the configuration + m_serviceConfig->sync(); + } +} + + +void RepositoryDialog::slotModifyClicked() +{ + slotDoubleClicked(m_repoList->selectedItem()); +} + + +void RepositoryDialog::slotRemoveClicked() +{ + // logout from pserver accounts so that they don't + // get re-added because of the .cvspass file. (BR #51129) + if( m_logoutButton->isEnabled() ) + slotLogoutClicked(); + + delete m_repoList->currentItem(); +} + + +void RepositoryDialog::slotDoubleClicked(QListViewItem* item) +{ + if( !item ) + return; + + RepositoryListItem* ritem = static_cast<RepositoryListItem*>(item); + QString repo = ritem->repository(); + QString rsh = ritem->rsh(); + QString server = ritem->server(); + int compression = ritem->compression(); + bool retrieveFile = ritem->retrieveCvsignore(); + + AddRepositoryDialog dlg(m_partConfig, repo, this); + dlg.setRepository(repo); + dlg.setRsh(rsh); + dlg.setServer(server); + dlg.setCompression(compression); + dlg.setRetrieveCvsignoreFile(retrieveFile); + if( dlg.exec() ) + { + ritem->setRsh(dlg.rsh()); + ritem->setServer(dlg.server()); + ritem->setCompression(dlg.compression()); + ritem->setRetrieveCvsignore(dlg.retrieveCvsignoreFile()); + + // write entries to cvs DCOP service configuration + writeRepositoryData(ritem); + + // write to disk so other services can reparse the configuration + m_serviceConfig->sync(); + } +} + + +void RepositoryDialog::slotLoginClicked() +{ + RepositoryListItem* item = (RepositoryListItem*)m_repoList->currentItem(); + if( !item ) + return; + + kdDebug(8050) << k_funcinfo << "repository = " << item->repository() << endl; + + DCOPRef job = m_cvsService->login(item->repository()); + if( !m_cvsService->ok() ) + { + kdError(8050) << "Failed to call login() method of the cvs DCOP service " + << "(" << m_cvsService->app() << ")" << endl; + return; + } + + bool success = job.call("execute()"); + if( !success ) + { + QStringList output = job.call("output()"); + KMessageBox::detailedError(this, i18n("Login failed."), output.join("\n")); + return; + } + + item->setIsLoggedIn(true); + slotSelectionChanged(); +} + + +void RepositoryDialog::slotLogoutClicked() +{ + RepositoryListItem* item = (RepositoryListItem*)m_repoList->currentItem(); + if( !item ) + return; + + kdDebug(8050) << k_funcinfo << "repository = " << item->repository() << endl; + + DCOPRef job = m_cvsService->logout(item->repository()); + if( !m_cvsService->ok() ) + { + kdError(8050) << "Failed to call logout() method of the cvs DCOP service " + << "(" << m_cvsService->app() << ")" << endl; + return; + } + + ProgressDialog dlg(this, "Logout", job, "logout", i18n("CVS Logout")); + if( !dlg.execute() ) + return; + + item->setIsLoggedIn(false); + slotSelectionChanged(); +} + + +void RepositoryDialog::slotSelectionChanged() +{ + // retrieve the selected item + RepositoryListItem* item = (RepositoryListItem*)m_repoList->selectedItem(); + + // is an item in the list view selected? + bool isItemSelected = (item != 0); + m_modifyButton->setEnabled(isItemSelected); + m_removeButton->setEnabled(isItemSelected); + m_loginButton->setEnabled(isItemSelected); + m_logoutButton->setEnabled(isItemSelected); + + if( !isItemSelected ) + return; + + // is this a pserver repository? + if( !LoginNeeded(item->repository()) ) + { + m_loginButton->setEnabled(false); + m_logoutButton->setEnabled(false); + return; + } + + // are we logged in? + bool isLoggedIn = item->isLoggedIn(); + m_loginButton->setEnabled(!isLoggedIn); + m_logoutButton->setEnabled(isLoggedIn); +} + + +void RepositoryDialog::writeRepositoryData(RepositoryListItem* item) +{ + // write entries to cvs DCOP service configuration + m_serviceConfig->setGroup(QString::fromLatin1("Repository-") + + item->repository()); + + m_serviceConfig->writeEntry("rsh", item->rsh()); + m_serviceConfig->writeEntry("cvs_server", item->server()); + m_serviceConfig->writeEntry("Compression", item->compression()); + m_serviceConfig->writeEntry("RetrieveCvsignore", item->retrieveCvsignore()); +} + +#include "repositorydlg.moc" + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/cervisia/repositorydlg.h b/cervisia/repositorydlg.h new file mode 100644 index 00000000..c980b712 --- /dev/null +++ b/cervisia/repositorydlg.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2003-2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef REPOSITORYDLG_H +#define REPOSITORYDLG_H + +#include <kdialogbase.h> + + +class QListViewItem; +class QPushButton; +class KConfig; +class KListView; +class CvsService_stub; +class RepositoryListItem; + + +class RepositoryDialog : public KDialogBase +{ + Q_OBJECT + +public: + RepositoryDialog(KConfig& cfg, CvsService_stub* cvsService, + QWidget* parent = 0, const char* name = 0); + virtual ~RepositoryDialog(); + + void readConfigFile(); + void readCvsPassFile(); + +protected: + virtual void slotOk(); + +private slots: + void slotAddClicked(); + void slotModifyClicked(); + void slotRemoveClicked(); + void slotDoubleClicked(QListViewItem *item); + void slotLoginClicked(); + void slotLogoutClicked(); + void slotSelectionChanged(); + +private: + void writeRepositoryData(RepositoryListItem* item); + +private: + KConfig& m_partConfig; + CvsService_stub* m_cvsService; + KConfig* m_serviceConfig; + KListView* m_repoList; + QPushButton* m_modifyButton; + QPushButton* m_removeButton; + QPushButton* m_loginButton; + QPushButton* m_logoutButton; +}; + +#endif + + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/cervisia/resolvedlg.cpp b/cervisia/resolvedlg.cpp new file mode 100644 index 00000000..2cf0ffd6 --- /dev/null +++ b/cervisia/resolvedlg.cpp @@ -0,0 +1,634 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2003-2004 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "resolvedlg.h" + +#include <qfile.h> +#include <qkeycode.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qtextcodec.h> +#include <qtextstream.h> +#include <kdebug.h> +#include <kfiledialog.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <qregexp.h> +#include "misc.h" +#include "resolvedlg_p.h" +using Cervisia::ResolveEditorDialog; + + +// *UGLY HACK* +// The following conditions are a rough hack +static QTextCodec *DetectCodec(const QString &fileName) +{ + if (fileName.endsWith(".ui") || fileName.endsWith(".docbook") + || fileName.endsWith(".xml")) + return QTextCodec::codecForName("utf8"); + + return QTextCodec::codecForLocale(); +} + + +namespace +{ + +class LineSeparator +{ +public: + LineSeparator(const QString& text) + : m_text(text) + , m_startPos(0) + , m_endPos(0) + { + } + + QString nextLine() const + { + // already reach end of text on previous call + if( m_endPos < 0 ) + { + m_currentLine = QString::null; + return m_currentLine; + } + + m_endPos = m_text.find('\n', m_startPos); + + int length = m_endPos - m_startPos + 1; + m_currentLine = m_text.mid(m_startPos, length); + m_startPos = m_endPos + 1; + + return m_currentLine; + } + + bool atEnd() const + { + return (m_endPos < 0 && m_currentLine.isEmpty()); + } + +private: + const QString m_text; + mutable QString m_currentLine; + mutable int m_startPos, m_endPos; +}; + +} + + +ResolveDialog::ResolveDialog(KConfig& cfg, QWidget *parent, const char *name) + : KDialogBase(parent, name, false, QString::null, + Close | Help | User1 | User2, Close, true, + KStdGuiItem::saveAs(), KStdGuiItem::save()) + , markeditem(-1) + , partConfig(cfg) +{ + items.setAutoDelete(true); + + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout *layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + QSplitter *vertSplitter = new QSplitter(QSplitter::Vertical, mainWidget); + + QSplitter *splitter = new QSplitter(QSplitter::Horizontal, vertSplitter); + + QWidget *versionALayoutWidget = new QWidget(splitter); + QBoxLayout *versionAlayout = new QVBoxLayout(versionALayoutWidget, 5); + + QLabel *revlabel1 = new QLabel(i18n("Your version (A):"), versionALayoutWidget); + versionAlayout->addWidget(revlabel1); + diff1 = new DiffView(cfg, true, false, versionALayoutWidget); + versionAlayout->addWidget(diff1, 10); + + QWidget* versionBLayoutWidget = new QWidget(splitter); + QBoxLayout *versionBlayout = new QVBoxLayout(versionBLayoutWidget, 5); + + QLabel *revlabel2 = new QLabel(i18n("Other version (B):"), versionBLayoutWidget); + versionBlayout->addWidget(revlabel2); + diff2 = new DiffView(cfg, true, false, versionBLayoutWidget); + versionBlayout->addWidget(diff2, 10); + + diff1->setPartner(diff2); + diff2->setPartner(diff1); + + QWidget* mergeLayoutWidget = new QWidget(vertSplitter); + QBoxLayout *mergeLayout = new QVBoxLayout(mergeLayoutWidget, 5); + + QLabel *mergelabel = new QLabel(i18n("Merged version:"), mergeLayoutWidget); + mergeLayout->addWidget(mergelabel); + + merge = new DiffView(cfg, false, false, mergeLayoutWidget); + mergeLayout->addWidget(merge, 10); + + layout->addWidget(vertSplitter); + + abutton = new QPushButton("&A", mainWidget); + connect( abutton, SIGNAL(clicked()), SLOT(aClicked()) ); + + bbutton = new QPushButton("&B", mainWidget); + connect( bbutton, SIGNAL(clicked()), SLOT(bClicked()) ); + + abbutton = new QPushButton("A+B", mainWidget); + connect( abbutton, SIGNAL(clicked()), SLOT(abClicked()) ); + + babutton = new QPushButton("B+A", mainWidget); + connect( babutton, SIGNAL(clicked()), SLOT(baClicked()) ); + + editbutton = new QPushButton(i18n("&Edit"), mainWidget); + connect( editbutton, SIGNAL(clicked()), SLOT(editClicked()) ); + + nofnlabel = new QLabel(mainWidget); + nofnlabel->setAlignment(AlignCenter); + + backbutton = new QPushButton("&<<", mainWidget); + connect( backbutton, SIGNAL(clicked()), SLOT(backClicked()) ); + + forwbutton = new QPushButton("&>>", mainWidget); + connect( forwbutton, SIGNAL(clicked()), SLOT(forwClicked()) ); + + QBoxLayout *buttonlayout = new QHBoxLayout(layout); + buttonlayout->addWidget(abutton, 1); + buttonlayout->addWidget(bbutton, 1); + buttonlayout->addWidget(abbutton, 1); + buttonlayout->addWidget(babutton, 1); + buttonlayout->addWidget(editbutton, 1); + buttonlayout->addStretch(1); + buttonlayout->addWidget(nofnlabel, 2); + buttonlayout->addStretch(1); + buttonlayout->addWidget(backbutton, 1); + buttonlayout->addWidget(forwbutton, 1); + + connect( this, SIGNAL(user2Clicked()), SLOT(saveClicked()) ); + connect( this, SIGNAL(user1Clicked()), SLOT(saveAsClicked()) ); + + QFontMetrics const fm(fontMetrics()); + setMinimumSize(fm.width('0') * 120, + fm.lineSpacing() * 40); + + setHelp("resolvingconflicts"); + + setWFlags(Qt::WDestructiveClose | getWFlags()); + + QSize size = configDialogSize(partConfig, "ResolveDialog"); + resize(size); +} + + +ResolveDialog::~ResolveDialog() +{ + saveDialogSize(partConfig, "ResolveDialog"); +} + + +// One resolve item has a line number range of linenoA:linenoA+linecountA-1 +// in A and linenoB:linenoB+linecountB-1 in B. If the user has chosen version A +// for the merged file (indicated by chosenA==true), then the line number +// range in the merged file is offsetM:offsetM+linecountA-1 (accordingly for +// the other case). +class ResolveItem +{ +public: + int linenoA, linecountA; + int linenoB, linecountB; + int linecountTotal; + int offsetM; + ResolveDialog::ChooseType chosen; +}; + + +bool ResolveDialog::parseFile(const QString &name) +{ + int lineno1, lineno2; + int advanced1, advanced2; + enum { Normal, VersionA, VersionB } state; + + setCaption(i18n("CVS Resolve: %1").arg(name)); + + fname = name; + + QString fileContent = readFile(); + if( fileContent.isNull() ) + return false; + + LineSeparator separator(fileContent); + + state = Normal; + lineno1 = lineno2 = 0; + advanced1 = advanced2 = 0; + do + { + QString line = separator.nextLine(); + + // reached end of file? + if( separator.atEnd() ) + break; + + switch( state ) + { + case Normal: + { + // check for start of conflict block + // Set to look for <<<<<<< at begining of line with exaclty one + // space after then anything after that. + QRegExp rx( "^<{7}\\s.*$" ); + int separatorPos = rx.search(line); + if( separatorPos >= 0 ) + { + state = VersionA; + advanced1 = 0; + } + else + { + addToMergeAndVersionA(line, DiffView::Unchanged, lineno1); + addToVersionB(line, DiffView::Unchanged, lineno2); + } + } + break; + case VersionA: + { + // Set to look for ======= at begining of line which may have one + // or more spaces after then nothing else. + QRegExp rx( "^={7}\\s*$" ); + int separatorPos = rx.search(line); + if( separatorPos < 0 ) // still in version A + { + advanced1++; + addToMergeAndVersionA(line, DiffView::Change, lineno1); + } + else + { + state = VersionB; + advanced2 = 0; + } + } + break; + case VersionB: + { + // Set to look for >>>>>>> at begining of line with exaclty one + // space after then anything after that. + QRegExp rx( "^>{7}\\s.*$" ); + int separatorPos = rx.search(line); + if( separatorPos < 0 ) // still in version B + { + advanced2++; + addToVersionB(line, DiffView::Change, lineno2); + } + else + { + // create an resolve item + ResolveItem *item = new ResolveItem; + item->linenoA = lineno1-advanced1+1; + item->linecountA = advanced1; + item->linenoB = lineno2-advanced2+1; + item->linecountB = advanced2; + item->offsetM = item->linenoA-1; + item->chosen = ChA; + item->linecountTotal = item->linecountA; + items.append(item); + + for (; advanced1 < advanced2; advanced1++) + diff1->addLine("", DiffView::Neutral); + for (; advanced2 < advanced1; advanced2++) + diff2->addLine("", DiffView::Neutral); + + state = Normal; + } + } + break; + } + } + while( !separator.atEnd() ); + + updateNofN(); + + return true; // succesful +} + + +void ResolveDialog::addToMergeAndVersionA(const QString& line, + DiffView::DiffType type, int& lineNo) +{ + lineNo++; + diff1->addLine(line, type, lineNo); + merge->addLine(line, type, lineNo); +} + + +void ResolveDialog::addToVersionB(const QString& line, DiffView::DiffType type, + int& lineNo) +{ + lineNo++; + diff2->addLine(line, type, lineNo); +} + + +void ResolveDialog::saveFile(const QString &name) +{ + QFile f(name); + if (!f.open(IO_WriteOnly)) + { + KMessageBox::sorry(this, + i18n("Could not open file for writing."), + "Cervisia"); + return; + } + QTextStream stream(&f); + QTextCodec *fcodec = DetectCodec(name); + stream.setCodec(fcodec); + + QString output; + for( int i = 0; i < merge->count(); i++ ) + output +=merge->stringAtOffset(i); + stream << output; + + f.close(); +} + + +QString ResolveDialog::readFile() +{ + QFile f(fname); + if( !f.open(IO_ReadOnly) ) + return QString::null; + + QTextStream stream(&f); + QTextCodec* codec = DetectCodec(fname); + stream.setCodec(codec); + + return stream.read(); +} + + +void ResolveDialog::updateNofN() +{ + QString str; + if (markeditem >= 0) + str = i18n("%1 of %2").arg(markeditem+1).arg(items.count()); + else + str = i18n("%1 conflicts").arg(items.count()); + nofnlabel->setText(str); + + backbutton->setEnabled(markeditem != -1); + forwbutton->setEnabled(markeditem != -2 && items.count()); + + bool marked = markeditem >= 0; + abutton->setEnabled(marked); + bbutton->setEnabled(marked); + abbutton->setEnabled(marked); + babutton->setEnabled(marked); + editbutton->setEnabled(marked); +} + + +void ResolveDialog::updateHighlight(int newitem) +{ + if (markeditem >= 0) + { + ResolveItem *item = items.at(markeditem); + for (int i = item->linenoA; i < item->linenoA+item->linecountA; ++i) + diff1->setInverted(i, false); + for (int i = item->linenoB; i < item->linenoB+item->linecountB; ++i) + diff2->setInverted(i, false); + } + + markeditem = newitem; + + if (markeditem >= 0) + { + ResolveItem *item = items.at(markeditem); + for (int i = item->linenoA; i < item->linenoA+item->linecountA; ++i) + diff1->setInverted(i, true); + for (int i = item->linenoB; i < item->linenoB+item->linecountB; ++i) + diff2->setInverted(i, true); + diff1->setCenterLine(item->linenoA); + diff2->setCenterLine(item->linenoB); + merge->setCenterOffset(item->offsetM); + } + diff1->repaint(); + diff2->repaint(); + merge->repaint(); + updateNofN(); +} + + +void ResolveDialog::updateMergedVersion(ResolveItem* item, + ResolveDialog::ChooseType chosen) +{ + // Remove old variant + for (int i = 0; i < item->linecountTotal; ++i) + merge->removeAtOffset(item->offsetM); + + // Insert new + int total = 0; + LineSeparator separator(m_contentMergedVersion); + QString line = separator.nextLine(); + while( !separator.atEnd() ) + { + merge->insertAtOffset(line, DiffView::Change, item->offsetM+total); + line = separator.nextLine(); + ++total; + } + + // Adjust other items + int difference = total - item->linecountTotal; + item->chosen = chosen; + item->linecountTotal = total; + while ( (item = items.next()) != 0 ) + item->offsetM += difference; + + merge->repaint(); +} + + +void ResolveDialog::backClicked() +{ + int newitem; + if (markeditem == -1) + return; // internal error (button not disabled) + else if (markeditem == -2) // past end + newitem = items.count()-1; + else + newitem = markeditem-1; + updateHighlight(newitem); +} + + +void ResolveDialog::forwClicked() +{ + int newitem; + if (markeditem == -2 || (markeditem == -1 && !items.count())) + return; // internal error (button not disabled) + else if (markeditem+1 == (int)items.count()) // past end + newitem = -2; + else + newitem = markeditem+1; + updateHighlight(newitem); +} + + +void ResolveDialog::choose(ChooseType ch) +{ + if (markeditem < 0) + return; + + ResolveItem *item = items.at(markeditem); + + switch (ch) + { + case ChA: + m_contentMergedVersion = contentVersionA(item); + break; + case ChB: + m_contentMergedVersion = contentVersionB(item); + break; + case ChAB: + m_contentMergedVersion = contentVersionA(item) + contentVersionB(item); + break; + case ChBA: + m_contentMergedVersion = contentVersionB(item) + contentVersionA(item); + break; + default: + kdDebug(8050) << "Internal error at switch" << endl; + } + + updateMergedVersion(item, ch); +} + + +void ResolveDialog::aClicked() +{ + choose(ChA); +} + + +void ResolveDialog::bClicked() +{ + choose(ChB); +} + + +void ResolveDialog::abClicked() +{ + choose(ChAB); +} + + +void ResolveDialog::baClicked() +{ + choose(ChBA); +} + + +void ResolveDialog::editClicked() +{ + if (markeditem < 0) + return; + + ResolveItem *item = items.at(markeditem); + + QString mergedPart; + int total = item->linecountTotal; + int offset = item->offsetM; + for( int i = 0; i < total; ++i ) + mergedPart += merge->stringAtOffset(offset+i); + + ResolveEditorDialog *dlg = new ResolveEditorDialog(partConfig, this, "edit"); + dlg->setContent(mergedPart); + + if (dlg->exec()) + { + m_contentMergedVersion = dlg->content(); + updateMergedVersion(item, ChEdit); + } + + delete dlg; + diff1->repaint(); + diff2->repaint(); + merge->repaint(); +} + + +void ResolveDialog::saveClicked() +{ + saveFile(fname); +} + + +void ResolveDialog::saveAsClicked() +{ + QString filename = + KFileDialog::getSaveFileName(0, 0, this, 0); + + if( !filename.isEmpty() && Cervisia::CheckOverwrite(filename) ) + saveFile(filename); +} + + +void ResolveDialog::keyPressEvent(QKeyEvent *e) +{ + switch (e->key()) + { + case Key_A: aClicked(); break; + case Key_B: bClicked(); break; + case Key_Left: backClicked(); break; + case Key_Right:forwClicked(); break; + case Key_Up: diff1->up(); break; + case Key_Down: diff1->down(); break; + default: + KDialogBase::keyPressEvent(e); + } +} + + + +/* This will return the A side of the diff in a QString. */ +QString ResolveDialog::contentVersionA(const ResolveItem *item) +{ + QString result; + for( int i = item->linenoA; i < item->linenoA+item->linecountA; ++i ) + { + result += diff1->stringAtLine(i); + } + + return result; +} + + +/* This will return the B side of the diff item in a QString. */ +QString ResolveDialog::contentVersionB(const ResolveItem *item) +{ + QString result; + for( int i = item->linenoB; i < item->linenoB+item->linecountB; ++i ) + { + result += diff2->stringAtLine(i); + } + + return result; +} + +#include "resolvedlg.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/resolvedlg.h b/cervisia/resolvedlg.h new file mode 100644 index 00000000..1cee22b7 --- /dev/null +++ b/cervisia/resolvedlg.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2003-2004 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef RESOLVEDLG_H +#define RESOLVEDLG_H + + +#include <kdialogbase.h> + +#include <qptrlist.h> +#include "diffview.h" + + +class QLabel; +class QTextCodec; +class KConfig; +class ResolveItem; + + +class ResolveDialog : public KDialogBase +{ + Q_OBJECT + +public: + enum ChooseType { ChA, ChB, ChAB, ChBA, ChEdit }; + + explicit ResolveDialog( KConfig& cfg, QWidget *parent=0, const char *name=0 ); + virtual ~ResolveDialog(); + + bool parseFile(const QString &name); + +protected: + virtual void keyPressEvent(QKeyEvent *e); + +private slots: + void backClicked(); + void forwClicked(); + void aClicked(); + void bClicked(); + void abClicked(); + void baClicked(); + void editClicked(); + void saveClicked(); + void saveAsClicked(); + +private: + void updateNofN(); + void updateHighlight(int newitem); + void choose(ChooseType ch); + void chooseEdit(); + void saveFile(const QString &name); + QString readFile(); + void addToMergeAndVersionA(const QString& line, DiffView::DiffType type, + int& lineNo); + void addToVersionB(const QString& line, DiffView::DiffType type, int& lineNo); + void updateMergedVersion(ResolveItem* item, ChooseType chosen); + QString contentVersionA(const ResolveItem *item); + QString contentVersionB(const ResolveItem *item); + + QLabel *nofnlabel; + QPushButton *backbutton, *forwbutton; + QPushButton *abutton, *bbutton, *abbutton, *babutton, *editbutton; + DiffView *diff1, *diff2, *merge; + + QPtrList<ResolveItem> items; + QString fname; + QTextCodec *fcodec; + int markeditem; + KConfig& partConfig; + + QString m_contentMergedVersion; +}; + + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/resolvedlg_p.cpp b/cervisia/resolvedlg_p.cpp new file mode 100644 index 00000000..e925677d --- /dev/null +++ b/cervisia/resolvedlg_p.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "resolvedlg_p.h" +using namespace Cervisia; + +#include <ktextedit.h> + + +ResolveEditorDialog::ResolveEditorDialog(KConfig& cfg, QWidget *parent, const char *name) + : KDialogBase(parent, name, true, QString::null, + Ok | Cancel, Ok, true) + , m_partConfig(cfg) +{ + m_edit = new KTextEdit(this); + m_edit->setFocus(); + + setMainWidget(m_edit); + + QFontMetrics const fm(fontMetrics()); + setMinimumSize(fm.width('0') * 120, + fm.lineSpacing() * 40); + + QSize size = configDialogSize(m_partConfig, "ResolveEditDialog"); + resize(size); +} + + +ResolveEditorDialog::~ResolveEditorDialog() +{ + saveDialogSize(m_partConfig, "ResolveEditDialog"); +} + + +void ResolveEditorDialog::setContent(const QString& text) +{ + m_edit->setText(text); +} + + +QString ResolveEditorDialog::content() const +{ + return m_edit->text(); +} diff --git a/cervisia/resolvedlg_p.h b/cervisia/resolvedlg_p.h new file mode 100644 index 00000000..30b4559a --- /dev/null +++ b/cervisia/resolvedlg_p.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef CERVISIA_RESOLVEEDITORDIALOG_H +#define CERVISIA_RESOLVEEDITORDIALOG_H + +#include <kdialogbase.h> + +class KTextEdit; +class QStringList; +class KConfig; + + +namespace Cervisia +{ + + +class ResolveEditorDialog : public KDialogBase +{ +public: + explicit ResolveEditorDialog(KConfig& cfg, QWidget* parent=0, const char* name=0); + virtual ~ResolveEditorDialog(); + + void setContent(const QString& text); + QString content() const; + +private: + KTextEdit* m_edit; + KConfig& m_partConfig; +}; + + +} + + +#endif diff --git a/cervisia/settingsdlg.cpp b/cervisia/settingsdlg.cpp new file mode 100644 index 00000000..71b8b32c --- /dev/null +++ b/cervisia/settingsdlg.cpp @@ -0,0 +1,385 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "settingsdlg.h" + +#include <qapplication.h> +#include <qcheckbox.h> +#include <qgrid.h> +#include <qgroupbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qvbox.h> +#include <qwidgetlist.h> +#include <qhbuttongroup.h> +#include <qradiobutton.h> +#include <kbuttonbox.h> +#include <kcolorbutton.h> +#include <kconfig.h> +#include <kfontdialog.h> +#include <kglobal.h> +#include <kiconloader.h> +#include <klineedit.h> +#include <klocale.h> +#include <knuminput.h> +#include <kurlrequester.h> + +#include "misc.h" +#include "cervisiasettings.h" +#include "settingsdlg_advanced.h" + + +namespace +{ + // helper method to load icons for configuration pages + inline QPixmap LoadIcon(const char* iconName) + { + KIconLoader* loader = KGlobal::instance()->iconLoader(); + return loader->loadIcon(QString::fromLatin1(iconName), KIcon::NoGroup, + KIcon::SizeMedium); + } +} + + +FontButton::FontButton( const QString &text, QWidget *parent, const char *name ) + : QPushButton(text, parent, name) +{ + connect( this, SIGNAL(clicked()), this, SLOT(chooseFont()) ); +} + + +void FontButton::chooseFont() +{ + QFont newFont(font()); + + if (KFontDialog::getFont(newFont, false, this) == QDialog::Rejected) + return; + + setFont(newFont); + repaint(false); +} + + +SettingsDialog::SettingsDialog( KConfig *conf, QWidget *parent, const char *name ) + : KDialogBase(KDialogBase::IconList, i18n("Configure Cervisia"), + KDialogBase::Ok | KDialogBase::Cancel | KDialogBase::Help, + KDialogBase::Ok, + parent, name, true) +{ + config = conf; + + // open cvs DCOP service configuration file + serviceConfig = new KConfig("cvsservicerc"); + + // + // General Options + // + addGeneralPage(); + + // + // Diff Options + // + addDiffPage(); + + // + // Status Options + // + addStatusPage(); + + // + // Advanced Options + // + addAdvancedPage(); + + // + // Look and Feel Options + // + addLookAndFeelPage(); + + readSettings(); + + setHelp("customization", "cervisia"); +} + +SettingsDialog::~SettingsDialog() +{ + delete serviceConfig; +} + +void SettingsDialog::readSettings() +{ + // read entries from cvs DCOP service configuration + serviceConfig->setGroup("General"); + cvspathedit->setURL(serviceConfig->readPathEntry("CVSPath", "cvs")); + m_advancedPage->kcfg_Compression->setValue(serviceConfig->readNumEntry( + "Compression", 0)); + m_advancedPage->kcfg_UseSshAgent->setChecked(serviceConfig->readBoolEntry( + "UseSshAgent", false)); + + config->setGroup("General"); + m_advancedPage->kcfg_Timeout->setValue(CervisiaSettings::timeout()); + usernameedit->setText(config->readEntry("Username", Cervisia::UserName())); + + contextedit->setValue((int)config->readUnsignedNumEntry("ContextLines", 65535)); + tabwidthedit->setValue((int)config->readUnsignedNumEntry("TabWidth", 8)); + diffoptedit->setText(config->readEntry("DiffOptions")); + extdiffedit->setURL(config->readPathEntry("ExternalDiff")); + remotestatusbox->setChecked(config->readBoolEntry("StatusForRemoteRepos", false)); + localstatusbox->setChecked(config->readBoolEntry("StatusForLocalRepos", false)); + + // read configuration for look and feel page + config->setGroup("LookAndFeel"); + m_protocolFontBox->setFont(config->readFontEntry("ProtocolFont")); + m_annotateFontBox->setFont(config->readFontEntry("AnnotateFont")); + m_diffFontBox->setFont(config->readFontEntry("DiffFont")); + m_changelogFontBox->setFont(config->readFontEntry("ChangeLogFont")); + m_splitterBox->setChecked(config->readBoolEntry("SplitHorizontally",true)); + + m_conflictButton->setColor(CervisiaSettings::conflictColor()); + m_localChangeButton->setColor(CervisiaSettings::localChangeColor()); + m_remoteChangeButton->setColor(CervisiaSettings::remoteChangeColor()); + m_notInCvsButton->setColor(CervisiaSettings::notInCvsColor()); + + m_diffChangeButton->setColor(CervisiaSettings::diffChangeColor()); + m_diffInsertButton->setColor(CervisiaSettings::diffInsertColor()); + m_diffDeleteButton->setColor(CervisiaSettings::diffDeleteColor()); +} + + +void SettingsDialog::writeSettings() +{ + // write entries to cvs DCOP service configuration + serviceConfig->setGroup("General"); + serviceConfig->writePathEntry("CVSPath", cvspathedit->url()); + serviceConfig->writeEntry("Compression", + m_advancedPage->kcfg_Compression->value()); + serviceConfig->writeEntry("UseSshAgent", + m_advancedPage->kcfg_UseSshAgent->isChecked()); + + // write to disk so other services can reparse the configuration + serviceConfig->sync(); + + config->setGroup("General"); + CervisiaSettings::setTimeout(m_advancedPage->kcfg_Timeout->value()); + config->writeEntry("Username", usernameedit->text()); + + config->writePathEntry("ExternalDiff", extdiffedit->url()); + + config->writeEntry("ContextLines", (unsigned)contextedit->value()); + config->writeEntry("TabWidth", tabwidthedit->value()); + config->writeEntry("DiffOptions", diffoptedit->text()); + config->writeEntry("StatusForRemoteRepos", remotestatusbox->isChecked()); + config->writeEntry("StatusForLocalRepos", localstatusbox->isChecked()); + + config->setGroup("LookAndFeel"); + config->writeEntry("ProtocolFont", m_protocolFontBox->font()); + config->writeEntry("AnnotateFont", m_annotateFontBox->font()); + config->writeEntry("DiffFont", m_diffFontBox->font()); + config->writeEntry("ChangeLogFont", m_changelogFontBox->font()); + config->writeEntry("SplitHorizontally", m_splitterBox->isChecked()); + + CervisiaSettings::setConflictColor(m_conflictButton->color()); + CervisiaSettings::setLocalChangeColor(m_localChangeButton->color()); + CervisiaSettings::setRemoteChangeColor(m_remoteChangeButton->color()); + CervisiaSettings::setNotInCvsColor(m_notInCvsButton->color()); + CervisiaSettings::setDiffChangeColor(m_diffChangeButton->color()); + CervisiaSettings::setDiffInsertColor(m_diffInsertButton->color()); + CervisiaSettings::setDiffDeleteColor(m_diffDeleteButton->color()); + + // I'm not yet sure whether this is a hack or not :-) + QWidgetListIt it(*QApplication::allWidgets()); + for (; it.current(); ++it) + { + QWidget *w = it.current(); + if (w->inherits("ProtocolView")) + w->setFont(m_protocolFontBox->font()); + if (w->inherits("AnnotateView")) + w->setFont(m_annotateFontBox->font()); + if (w->inherits("DiffView")) + w->setFont(m_diffFontBox->font()); + } + config->sync(); + + CervisiaSettings::writeConfig(); +} + +void SettingsDialog::done(int res) +{ + if (res == Accepted) + writeSettings(); + KDialogBase::done(res); + delete this; +} + + +/* + * Create a page for the general options + */ +void SettingsDialog::addGeneralPage() +{ + QFrame* generalPage = addPage(i18n("General"), QString::null, + LoadIcon("misc")); + QVBoxLayout* layout = new QVBoxLayout(generalPage, 0, KDialog::spacingHint()); + + QLabel *usernamelabel = new QLabel( i18n("&User name for the change log editor:"), generalPage ); + usernameedit = new KLineEdit(generalPage); + usernameedit->setFocus(); + usernamelabel->setBuddy(usernameedit); + + layout->addWidget(usernamelabel); + layout->addWidget(usernameedit); + + QLabel *cvspathlabel = new QLabel( i18n("&Path to CVS executable, or 'cvs':"), generalPage ); + cvspathedit = new KURLRequester(generalPage); + cvspathlabel->setBuddy(cvspathedit); + + layout->addWidget(cvspathlabel); + layout->addWidget(cvspathedit); + + layout->addStretch(); +} + + +/* + * Create a page for the diff optionsw + */ +void SettingsDialog::addDiffPage() +{ + QGrid *diffPage = addGridPage(2, QGrid::Horizontal, i18n("Diff Viewer"), + QString::null, LoadIcon("vcs_diff")); + + QLabel *contextlabel = new QLabel( i18n("&Number of context lines in diff dialog:"), diffPage ); + contextedit = new KIntNumInput( 0, diffPage ); + contextedit->setRange(0, 65535, 1, false); + contextlabel->setBuddy(contextedit); + + QLabel *diffoptlabel = new QLabel(i18n("Additional &options for cvs diff:"), diffPage); + diffoptedit = new KLineEdit(diffPage); + diffoptlabel->setBuddy(diffoptedit); + + QLabel *tabwidthlabel = new QLabel(i18n("Tab &width in diff dialog:"), diffPage); + tabwidthedit = new KIntNumInput(0, diffPage); + tabwidthedit->setRange(1, 16, 1, false); + tabwidthlabel->setBuddy(tabwidthedit); + + QLabel *extdifflabel = new QLabel(i18n("External diff &frontend:"), diffPage); + extdiffedit = new KURLRequester(diffPage); + extdifflabel->setBuddy(extdiffedit); + + // dummy widget to take up the vertical space + new QWidget(diffPage); +} + + +/* + * Create a page for the status options + */ +void SettingsDialog::addStatusPage() +{ + QVBox* statusPage = addVBoxPage(i18n("Status"), QString::null, + LoadIcon("fork")); + + remotestatusbox = new QCheckBox(i18n("When opening a sandbox from a &remote repository,\n" + "start a File->Status command automatically"), statusPage); + localstatusbox = new QCheckBox(i18n("When opening a sandbox from a &local repository,\n" + "start a File->Status command automatically"), statusPage); + + // dummy widget to take up the vertical space + new QWidget(statusPage); +} + + +/* + * Create a page for the advanced options + */ +void SettingsDialog::addAdvancedPage() +{ + QVBox* frame = addVBoxPage(i18n("Advanced"), QString::null, + LoadIcon("configure")); + + m_advancedPage = new AdvancedPage(frame); + m_advancedPage->kcfg_Timeout->setRange(0, 50000, 100, false); + m_advancedPage->kcfg_Compression->setRange(0, 9, 1, false); +} + + +/* + * Create a page for the look & feel options + */ +void SettingsDialog::addLookAndFeelPage() +{ + QVBox* lookPage = addVBoxPage(i18n("Appearance"), QString::null, + LoadIcon("looknfeel")); + + QGroupBox* fontGroupBox = new QGroupBox(4, Qt::Vertical, i18n("Fonts"), + lookPage); + fontGroupBox->setInsideSpacing(KDialog::spacingHint()); + + m_protocolFontBox = new FontButton(i18n("Font for &Protocol Window..."), + fontGroupBox); + m_annotateFontBox = new FontButton(i18n("Font for A&nnotate View..."), + fontGroupBox); + m_diffFontBox = new FontButton(i18n("Font for D&iff View..."), + fontGroupBox); + m_changelogFontBox = new FontButton(i18n("Font for ChangeLog View..."), + fontGroupBox); + + QGroupBox* colorGroupBox = new QGroupBox(4, Qt::Horizontal, + i18n("Colors"), lookPage); + colorGroupBox->setColumns(4); + colorGroupBox->setInsideSpacing(KDialog::spacingHint()); + + QLabel* conflictLabel = new QLabel(i18n("Conflict:"), colorGroupBox); + m_conflictButton = new KColorButton(colorGroupBox); + conflictLabel->setBuddy(m_conflictButton); + + QLabel* diffChangeLabel = new QLabel(i18n("Diff change:"), colorGroupBox); + m_diffChangeButton = new KColorButton(colorGroupBox); + diffChangeLabel->setBuddy(m_diffChangeButton); + + QLabel* localChangeLabel = new QLabel(i18n("Local change:"), colorGroupBox); + m_localChangeButton = new KColorButton(colorGroupBox); + localChangeLabel->setBuddy(m_localChangeButton); + + QLabel* diffInsertLabel = new QLabel(i18n("Diff insertion:"), colorGroupBox); + m_diffInsertButton = new KColorButton(colorGroupBox); + diffInsertLabel->setBuddy(m_diffInsertButton); + + QLabel* remoteChangeLabel = new QLabel(i18n("Remote change:"), colorGroupBox); + m_remoteChangeButton = new KColorButton(colorGroupBox); + remoteChangeLabel->setBuddy( m_remoteChangeButton ); + + QLabel* diffDeleteLabel = new QLabel(i18n("Diff deletion:"), colorGroupBox); + m_diffDeleteButton = new KColorButton(colorGroupBox); + diffDeleteLabel->setBuddy(m_diffDeleteButton); + + QLabel* notInCvsLabel = new QLabel(i18n("Not in cvs:"), colorGroupBox); + m_notInCvsButton = new KColorButton(colorGroupBox); + notInCvsLabel->setBuddy(m_notInCvsButton); + + m_splitterBox = new QCheckBox(i18n("Split main window &horizontally"), lookPage); +} + +#include "settingsdlg.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/settingsdlg.h b/cervisia/settingsdlg.h new file mode 100644 index 00000000..2f7effb4 --- /dev/null +++ b/cervisia/settingsdlg.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SETTINGSDLG_H +#define SETTINGSDLG_H + + +#include <qpushbutton.h> +#include <kdialogbase.h> + + +class QCheckBox; +class KIntNumInput; +class KLineEdit; +class KConfig; +class KColorButton; +class KURLRequester; +class AdvancedPage; + + +class FontButton : public QPushButton +{ + Q_OBJECT + +public: + FontButton( const QString &text, QWidget *parent=0, const char *name=0 ); + +private slots: + void chooseFont(); +}; + + +class SettingsDialog : public KDialogBase +{ + Q_OBJECT + +public: + SettingsDialog( KConfig *conf, QWidget *parent=0, const char *name=0 ); + virtual ~SettingsDialog(); + +protected slots: + virtual void done(int res); + +private: + void readSettings(); + void writeSettings(); + + void addGeneralPage(); + void addDiffPage(); + void addStatusPage(); + void addAdvancedPage(); + void addLookAndFeelPage(); + + KConfig *config; + KIntNumInput *contextedit; + KIntNumInput *tabwidthedit; + KURLRequester *cvspathedit; + KLineEdit *usernameedit; + KLineEdit *diffoptedit; + KURLRequester *extdiffedit; + QCheckBox *remotestatusbox; + QCheckBox *localstatusbox; + FontButton* m_protocolFontBox; + FontButton* m_annotateFontBox; + FontButton* m_diffFontBox; + FontButton* m_changelogFontBox; + + KColorButton* m_conflictButton; + KColorButton* m_localChangeButton; + KColorButton* m_remoteChangeButton; + KColorButton* m_notInCvsButton; + KColorButton* m_diffChangeButton; + KColorButton* m_diffInsertButton; + KColorButton* m_diffDeleteButton; + + QCheckBox* m_splitterBox; + AdvancedPage* m_advancedPage; + + KConfig* serviceConfig; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/settingsdlg_advanced.ui b/cervisia/settingsdlg_advanced.ui new file mode 100644 index 00000000..232c2678 --- /dev/null +++ b/cervisia/settingsdlg_advanced.ui @@ -0,0 +1,97 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>AdvancedPage</class> +<widget class="QWidget"> + <property name="name"> + <cstring>advancedPage</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>575</width> + <height>290</height> + </rect> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer row="3" column="1"> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>31</width> + <height>41</height> + </size> + </property> + </spacer> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>timeoutLbl</cstring> + </property> + <property name="text"> + <string>&Timeout after which a progress dialog appears (in ms):</string> + </property> + <property name="buddy" stdset="0"> + <cstring>kcfg_Timeout</cstring> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>compressionLbl</cstring> + </property> + <property name="text"> + <string>Default compression &level:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>kcfg_Compression</cstring> + </property> + </widget> + <widget class="QCheckBox" row="2" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>kcfg_UseSshAgent</cstring> + </property> + <property name="text"> + <string>Utilize a running or start a new ssh-agent process</string> + </property> + </widget> + <widget class="KIntNumInput" row="1" column="1"> + <property name="name"> + <cstring>kcfg_Compression</cstring> + </property> + <property name="minValue"> + <number>0</number> + </property> + <property name="maxValue"> + <number>9</number> + </property> + </widget> + <widget class="KIntNumInput" row="0" column="1"> + <property name="name"> + <cstring>kcfg_Timeout</cstring> + </property> + <property name="minValue"> + <number>0</number> + </property> + <property name="maxValue"> + <number>50000</number> + </property> + </widget> + </grid> +</widget> +<layoutdefaults spacing="6" margin="0"/> +<includehints> + <includehint>knuminput.h</includehint> + <includehint>knuminput.h</includehint> + <includehint>knuminput.h</includehint> + <includehint>knuminput.h</includehint> +</includehints> +</UI> diff --git a/cervisia/stringmatcher.cpp b/cervisia/stringmatcher.cpp new file mode 100644 index 00000000..1f0b4de8 --- /dev/null +++ b/cervisia/stringmatcher.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "stringmatcher.h" + +// For some reason fnmatch is defined as ap_fnmatch +#define ap_fnmatch fnmatch +#include <fnmatch.h> + + +namespace Cervisia +{ +namespace +{ + const QChar asterix('*'); + const QChar question('?'); + + inline bool isMetaCharacter(QChar c) + { + return c == asterix || c == question; + } + + + unsigned int countMetaCharacters(const QString& text); +} + + +bool StringMatcher::match(const QString& text) const +{ + if (m_exactPatterns.find(text) != m_exactPatterns.end()) + { + return true; + } + + for (QStringList::const_iterator it(m_startPatterns.begin()), + itEnd(m_startPatterns.end()); + it != itEnd; ++it) + { + if (text.startsWith(*it)) + { + return true; + } + } + + for (QStringList::const_iterator it(m_endPatterns.begin()), + itEnd(m_endPatterns.end()); + it != itEnd; ++it) + { + if (text.endsWith(*it)) + { + return true; + } + } + + for (QValueList<QCString>::const_iterator it(m_generalPatterns.begin()), + itEnd(m_generalPatterns.end()); + it != itEnd; ++it) + { + if (::fnmatch(*it, text.local8Bit(), FNM_PATHNAME) == 0) + { + return true; + } + } + + return false; +} + + +void StringMatcher::add(const QString& pattern) +{ + if (pattern.isEmpty()) + { + return; + } + + const int lengthMinusOne(pattern.length() - 1); + switch (countMetaCharacters(pattern)) + { + case 0: + m_exactPatterns.push_back(pattern); + break; + + case 1: + if (pattern.constref(0) == asterix) + { + m_endPatterns.push_back(pattern.right(lengthMinusOne)); + } + else if (pattern.constref(lengthMinusOne) == asterix) + { + m_startPatterns.push_back(pattern.left(lengthMinusOne)); + } + else + { + m_generalPatterns.push_back(pattern.local8Bit()); + } + break; + + default: + m_generalPatterns.push_back(pattern.local8Bit()); + break; + } +} + + +void StringMatcher::clear() +{ + m_exactPatterns.clear(); + m_startPatterns.clear(); + m_endPatterns.clear(); + m_generalPatterns.clear(); +} + + +namespace +{ +unsigned int countMetaCharacters(const QString& text) +{ + unsigned int count(0); + + const QChar* pos(text.unicode()); + const QChar* posEnd(pos + text.length()); + while (pos < posEnd) + { + count += isMetaCharacter(*pos++); + } + + return count; +} +} +} // namespace Cervisia diff --git a/cervisia/stringmatcher.h b/cervisia/stringmatcher.h new file mode 100644 index 00000000..17ecd3e8 --- /dev/null +++ b/cervisia/stringmatcher.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef CERVISIA_STRINGMATCHER_H +#define CERVISIA_STRINGMATCHER_H + + +#include <qstringlist.h> + + +namespace Cervisia +{ + + +class StringMatcher +{ +public: + + /** + * @return \c true, if text matches one of the given patterns. + */ + bool match(const QString& text) const; + + /** + * Adds pattern \a pattern. + */ + void add(const QString& pattern); + + /** + * Removes all patterns. + */ + void clear(); + +private: + + /** + * The patterns which are tested in match(). + */ + QStringList m_exactPatterns; + + /** + * The patterns which are tested in match(). + */ + QStringList m_startPatterns; + + /** + * The patterns which are tested in match(). + */ + QStringList m_endPatterns; + + /** + * The patterns which are tested in match(). + */ + QValueList<QCString> m_generalPatterns; +}; + + +} // namespace Cervisia + + +#endif // CERVISIA_STRINGMATCHER_H diff --git a/cervisia/tagdlg.cpp b/cervisia/tagdlg.cpp new file mode 100644 index 00000000..2f1e7e89 --- /dev/null +++ b/cervisia/tagdlg.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "tagdlg.h" + +#include <qcheckbox.h> +#include <qcombobox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <qpushbutton.h> +#include <klocale.h> +#include <kmessagebox.h> + +#include "misc.h" +#include "cvsservice_stub.h" + +using Cervisia::TagDialog; + +TagDialog::TagDialog(ActionType action, CvsService_stub* service, + QWidget *parent, const char *name) + : KDialogBase(parent, name, true, QString::null, + Ok | Cancel | Help, Ok, true), + act(action), + cvsService(service), + branchtag_button(0), + forcetag_button(0) +{ + setCaption( (action==Delete)? i18n("CVS Delete Tag") : i18n("CVS Tag") ); + + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout *layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + if (action == Delete) + { + tag_combo = new QComboBox(true, mainWidget); + tag_combo->setFocus(); + tag_combo->setMinimumWidth(fontMetrics().width('0') * 30); + + QLabel *tag_label = new QLabel(tag_combo, i18n("&Name of tag:"), mainWidget); + + QPushButton *tag_button = new QPushButton(i18n("Fetch &List"), mainWidget); + connect( tag_button, SIGNAL(clicked()), + this, SLOT(tagButtonClicked()) ); + + QBoxLayout *tagedit_layout = new QHBoxLayout(layout); + tagedit_layout->addWidget(tag_label); + tagedit_layout->addWidget(tag_combo); + tagedit_layout->addWidget(tag_button); + } + else + { + tag_edit = new QLineEdit(mainWidget); + tag_edit->setFocus(); + tag_edit->setMinimumWidth(fontMetrics().width('0') * 30); + + QLabel *tag_label = new QLabel(tag_edit, i18n("&Name of tag:"), mainWidget); + + QBoxLayout *tagedit_layout = new QHBoxLayout(layout); + tagedit_layout->addWidget(tag_label); + tagedit_layout->addWidget(tag_edit); + + branchtag_button = new QCheckBox(i18n("Create &branch with this tag"), mainWidget); + layout->addWidget(branchtag_button); + + forcetag_button = new QCheckBox(i18n("&Force tag creation even if tag already exists"), mainWidget); + layout->addWidget(forcetag_button); + } + + setHelp("taggingbranching"); +} + + +bool TagDialog::branchTag() const +{ + return branchtag_button && branchtag_button->isChecked(); +} + + +bool TagDialog::forceTag() const +{ + return forcetag_button && forcetag_button->isChecked(); +} + + +QString TagDialog::tag() const +{ + return act==Delete? tag_combo->currentText() : tag_edit->text(); +} + + +void TagDialog::slotOk() +{ + QString const str(tag()); + + if (str.isEmpty()) + { + KMessageBox::sorry(this, + i18n("You must define a tag name."), + "Cervisia"); + return; + } + + if (!Cervisia::IsValidTag(str)) + { + KMessageBox::sorry(this, + i18n("Tag must start with a letter and may contain " + "letters, digits and the characters '-' and '_'."), + "Cervisia"); + return; + } + + KDialogBase::slotOk(); +} + + +void TagDialog::tagButtonClicked() +{ + tag_combo->clear(); + tag_combo->insertStringList(::fetchTags(cvsService, this)); +} + + +#include "tagdlg.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/tagdlg.h b/cervisia/tagdlg.h new file mode 100644 index 00000000..4d1e4754 --- /dev/null +++ b/cervisia/tagdlg.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef TAGDLG_H +#define TAGDLG_H + + +#include <kdialogbase.h> + + +class QCheckBox; +class QComboBox; +class QLineEdit; +class CvsService_stub; + +namespace Cervisia +{ + +class TagDialog : public KDialogBase +{ + Q_OBJECT + +public: + enum ActionType { Create, Delete }; + + TagDialog( ActionType action, CvsService_stub* service, + QWidget *parent=0, const char *name=0 ); + + bool branchTag() const; + bool forceTag() const; + QString tag() const; + +protected: + virtual void slotOk(); + +private slots: + void tagButtonClicked(); + +private: + ActionType act; + CvsService_stub* cvsService; + + QCheckBox *branchtag_button; + QCheckBox *forcetag_button; + QLineEdit *tag_edit; + QComboBox *tag_combo; +}; + +} + + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/tests/resolvedlg-BR74903-test-01.txt b/cervisia/tests/resolvedlg-BR74903-test-01.txt new file mode 100644 index 00000000..c4b8a22f --- /dev/null +++ b/cervisia/tests/resolvedlg-BR74903-test-01.txt @@ -0,0 +1,4 @@ +123 +<<<<<<< resolvedlg-BR74903-test-01.txt +conf======= +456>>>>>>> 1.2 diff --git a/cervisia/tests/resolvedlg-conflict-test-01.txt b/cervisia/tests/resolvedlg-conflict-test-01.txt new file mode 100644 index 00000000..bbcd08c3 --- /dev/null +++ b/cervisia/tests/resolvedlg-conflict-test-01.txt @@ -0,0 +1,5 @@ +<<<<<<< resolvedlg-conflict-test-01.txt +Line 1 - modified on CONFLICT_BRANCH +======= +Line 1 - modified on HEAD +>>>>>>> 1.2 diff --git a/cervisia/tests/resolvedlg-conflict-test-02.txt b/cervisia/tests/resolvedlg-conflict-test-02.txt new file mode 100644 index 00000000..5d72025e --- /dev/null +++ b/cervisia/tests/resolvedlg-conflict-test-02.txt @@ -0,0 +1,9 @@ +Line 1 +Line 2 +<<<<<<< resolvedlg-conflict-test-02.txt +Line 3 - modified on CONFLICT_BRANCH +======= +Line 3 - modified on HEAD +>>>>>>> 1.2 +Line 4 +Line 5 diff --git a/cervisia/tooltip.cpp b/cervisia/tooltip.cpp new file mode 100644 index 00000000..7debf9d7 --- /dev/null +++ b/cervisia/tooltip.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2004-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "tooltip.h" + +#include <kglobal.h> +#include <kglobalsettings.h> + +#include <qsimplerichtext.h> + + +namespace Cervisia +{ + + +static QString truncateLines(const QString&, const QFontMetrics&, const QSize&); +static QString truncateLines(const QString&, const QFont&, const QPoint&, const QRect&); + + +ToolTip::ToolTip(QWidget* widget) + : QObject(widget), QToolTip(widget) +{ +} + + +void ToolTip::maybeTip(const QPoint& pos) +{ + QRect rect; + QString text; + emit queryToolTip(pos, rect, text); + + if (rect.isValid() && !text.isEmpty()) + { + text = truncateLines(text, + font(), + parentWidget()->mapToGlobal(pos), + KGlobalSettings::desktopGeometry(parentWidget())); + tip(rect, text); + } +} + + +// Primtive routine to truncate the text. size.width() is ignored, only +// size.height() is used at the moment to keep it fast. It doesn't work +// correct if text lines have different heights. +QString truncateLines(const QString& text, + const QFontMetrics& fm, + const QSize& size) +{ + const QChar newLine('\n'); + + const int lineSpacing(fm.lineSpacing()); + const int numberOfLines(text.contains(newLine) + 1); + const int maxNumberOfLines(size.height() / lineSpacing); + + if (numberOfLines <= maxNumberOfLines) + return text; + + const QChar* unicode(text.unicode()); + for (int count(maxNumberOfLines); count; ++unicode) + if (*unicode == newLine) + --count; + + return text.left(unicode - text.unicode() - 1); +} + + +// Truncate the tooltip's text if necessary +QString truncateLines(const QString& text, + const QFont& font, + const QPoint& globalPos, + const QRect& desktopGeometry) +{ + // maximum size of the tooltip, - 10 just to be safe + const int maxWidth(kMax(desktopGeometry.width() - globalPos.x(), globalPos.x()) + - desktopGeometry.left() - 10); + const int maxHeight(kMax(desktopGeometry.height() - globalPos.y(), globalPos.y()) + - desktopGeometry.top() - 10); + + // calculate the tooltip's size + const QSimpleRichText layoutedText(text, font); + + // only if the tooltip's size is bigger in x- and y-direction the text must + // be truncated otherwise the tip is moved to a position where it fits + return ((layoutedText.widthUsed() > maxWidth) + && (layoutedText.height() > maxHeight)) + ? truncateLines(text, QFontMetrics(font), QSize(maxWidth, maxHeight)) + : text; +} + + +} // namespace Cervisia + + +#include "tooltip.moc" diff --git a/cervisia/tooltip.h b/cervisia/tooltip.h new file mode 100644 index 00000000..fc0cd7be --- /dev/null +++ b/cervisia/tooltip.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2004-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef CERVISIA_TOOLTIP_H +#define CERVISIA_TOOLTIP_H + + +#include <qobject.h> +#include <qtooltip.h> + + +namespace Cervisia +{ + + +/** + * This class extends QToolTip: + * - no more need to subclass just connect to the signal queryToolTip() + * - truncate too large tooltip texts. + */ +class ToolTip : public QObject, public QToolTip +{ + Q_OBJECT + +public: + + /** + * @param widget The widget you want to add tooltips to. It's also used as + * parent for the QObject. So you don't have to free an instance of this + * class yourself. + */ + explicit ToolTip(QWidget* widget); + +signals: + + /** + * This signal is emitted when a tooltip could be displayed. When a client + * wants to display anythink it must set a valid tooltip rectangle and a + * non empty text. + * + * @param pos The position of the tooltip in the parent widget's coordinate system. + * + * @param rect The rectangle in the parent widget's coordinate system where the + * tooltip is valid. + * + * @param text The tooltip text. + */ + void queryToolTip(const QPoint& pos, QRect& rect, QString& text); + +protected: + + virtual void maybeTip(const QPoint&); +}; + + +} // namespace Cervisia + + +#endif // CERVISIA_TOOLTIP_H diff --git a/cervisia/updatedlg.cpp b/cervisia/updatedlg.cpp new file mode 100644 index 00000000..9a536763 --- /dev/null +++ b/cervisia/updatedlg.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "updatedlg.h" + +#include <qbuttongroup.h> +#include <qcombobox.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qradiobutton.h> +#include <qstyle.h> +#include <klineedit.h> +#include <klocale.h> + +#include "misc.h" +#include "cvsservice_stub.h" + + +UpdateDialog::UpdateDialog(CvsService_stub* service, + QWidget *parent, const char *name) + : KDialogBase(parent, name, true, i18n("CVS Update"), + Ok | Cancel, Ok, true), + cvsService(service) +{ + int const iComboBoxMinWidth(40 * fontMetrics().width('0')); + int const iWidgetIndent(style().pixelMetric(QStyle::PM_ExclusiveIndicatorWidth, 0) + 6); + + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout *layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + bybranch_button = new QRadioButton(i18n("Update to &branch: "), mainWidget); + bybranch_button->setChecked(true); + layout->addWidget(bybranch_button); + + branch_combo = new QComboBox(true, mainWidget); + branch_combo->setMinimumWidth(iComboBoxMinWidth); + + branch_button = new QPushButton(i18n("Fetch &List"), mainWidget); + connect( branch_button, SIGNAL(clicked()), + this, SLOT(branchButtonClicked()) ); + + QBoxLayout *branchedit_layout = new QHBoxLayout(layout); + branchedit_layout->addSpacing(iWidgetIndent); + branchedit_layout->addWidget(branch_combo); + branchedit_layout->addWidget(branch_button); + + bytag_button = new QRadioButton(i18n("Update to &tag: "), mainWidget); + layout->addWidget(bytag_button); + + tag_combo = new QComboBox(true, mainWidget); + tag_combo->setMinimumWidth(iComboBoxMinWidth); + + tag_button = new QPushButton(i18n("Fetch L&ist"), mainWidget); + connect( tag_button, SIGNAL(clicked()), + this, SLOT(tagButtonClicked()) ); + + QBoxLayout *tagedit_layout = new QHBoxLayout(layout); + tagedit_layout->addSpacing(iWidgetIndent); + tagedit_layout->addWidget(tag_combo); + tagedit_layout->addWidget(tag_button); + + bydate_button = new QRadioButton(i18n("Update to &date ('yyyy-mm-dd'):"), mainWidget); + layout->addWidget(bydate_button); + + date_edit = new KLineEdit(mainWidget); + + QBoxLayout *dateedit_layout = new QHBoxLayout(layout); + dateedit_layout->addSpacing(iWidgetIndent); + dateedit_layout->addWidget(date_edit); + + QButtonGroup* group = new QButtonGroup(mainWidget); + group->hide(); + group->insert(bytag_button); + group->insert(bybranch_button); + group->insert(bydate_button); + connect( group, SIGNAL(clicked(int)), + this, SLOT(toggled()) ); + + // dis-/enable the widgets + toggled(); +} + + +bool UpdateDialog::byTag() const +{ + return bybranch_button->isChecked() || bytag_button->isChecked(); +} + + +QString UpdateDialog::tag() const +{ + return bybranch_button->isChecked() + ? branch_combo->currentText() + : tag_combo->currentText(); +} + + +QString UpdateDialog::date() const +{ + return date_edit->text(); +} + + +void UpdateDialog::tagButtonClicked() +{ + tag_combo->clear(); + tag_combo->insertStringList(::fetchTags(cvsService, this)); +} + + +void UpdateDialog::branchButtonClicked() +{ + branch_combo->clear(); + branch_combo->insertStringList(::fetchBranches(cvsService, this)); +} + + +void UpdateDialog::toggled() +{ + bool bytag = bytag_button->isChecked(); + tag_combo->setEnabled(bytag); + tag_button->setEnabled(bytag); + if (bytag) + tag_combo->setFocus(); + + bool bybranch = bybranch_button->isChecked(); + branch_combo->setEnabled(bybranch); + branch_button->setEnabled(bybranch); + if (bybranch) + branch_combo->setFocus(); + + bool bydate = bydate_button->isChecked(); + date_edit->setEnabled(bydate); + if (bydate) + date_edit->setFocus(); +} + +#include "updatedlg.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/updatedlg.h b/cervisia/updatedlg.h new file mode 100644 index 00000000..2ce143cf --- /dev/null +++ b/cervisia/updatedlg.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef UPDATEDLG_H +#define UPDATEDLG_H + + +#include <kdialogbase.h> + + +class QComboBox; +class QPushButton; +class QRadioButton; +class KLineEdit; +class CvsService_stub; + + +class UpdateDialog : public KDialogBase +{ + Q_OBJECT + +public: + UpdateDialog( CvsService_stub* service, + QWidget *parent=0, const char *name=0 ); + + bool byTag() const; + QString tag() const; + QString date() const; + +private slots: + void toggled(); + void tagButtonClicked(); + void branchButtonClicked(); + +private: + CvsService_stub* cvsService; + + QRadioButton *bytag_button, *bybranch_button, *bydate_button; + QComboBox *tag_combo, *branch_combo; + QPushButton *tag_button, *branch_button; + KLineEdit *date_edit; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/updateview.cpp b/cervisia/updateview.cpp new file mode 100644 index 00000000..94679f6c --- /dev/null +++ b/cervisia/updateview.cpp @@ -0,0 +1,629 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann <bernd@mail.berlios.de> + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "updateview.h" + +#include <set> + +#include <qapplication.h> +#include <qfileinfo.h> +#include <qptrstack.h> +#include <kconfig.h> +#include <klocale.h> + +#include "cervisiasettings.h" +#include "entry.h" +#include "updateview_items.h" +#include "updateview_visitors.h" + + +using Cervisia::Entry; +using Cervisia::EntryStatus; + + +UpdateView::UpdateView(KConfig& partConfig, QWidget *parent, const char *name) + : KListView(parent, name), + m_partConfig(partConfig), + m_unfoldingTree(false) +{ + setAllColumnsShowFocus(true); + setShowSortIndicator(true); + setSelectionModeExt(Extended); + + addColumn(i18n("File Name"), 280); + addColumn(i18n("File Type"), 180); + addColumn(i18n("Status"), 90); + addColumn(i18n("Revision"), 70); + addColumn(i18n("Tag/Date"), 90); + addColumn(i18n("Timestamp"), 120); + + setFilter(NoFilter); + + connect( this, SIGNAL(doubleClicked(QListViewItem*)), + this, SLOT(itemExecuted(QListViewItem*)) ); + connect( this, SIGNAL(returnPressed(QListViewItem*)), + this, SLOT(itemExecuted(QListViewItem*)) ); + + // without this restoreLayout() can't change the column widths + for (int col = 0; col < columns(); ++col) + setColumnWidthMode(col, QListView::Manual); + + restoreLayout(&m_partConfig, QString::fromLatin1("UpdateView")); +} + + +UpdateView::~UpdateView() +{ + saveLayout(&m_partConfig, QString::fromLatin1("UpdateView")); +} + + +void UpdateView::setFilter(Filter filter) +{ + filt = filter; + + if (UpdateDirItem* item = static_cast<UpdateDirItem*>(firstChild())) + { + ApplyFilterVisitor applyFilterVisitor(filter); + item->accept(applyFilterVisitor); + } + + setSorting(columnSorted(), ascendingSort()); +} + + +UpdateView::Filter UpdateView::filter() const +{ + return filt; +} + + +// returns true iff exactly one UpdateFileItem is selected +bool UpdateView::hasSingleSelection() const +{ + const QPtrList<QListViewItem>& listSelectedItems(selectedItems()); + + return (listSelectedItems.count() == 1) && isFileItem(listSelectedItems.getFirst()); +} + + +void UpdateView::getSingleSelection(QString *filename, QString *revision) const +{ + const QPtrList<QListViewItem>& listSelectedItems(selectedItems()); + + QString tmpFileName; + QString tmpRevision; + if ((listSelectedItems.count() == 1) && isFileItem(listSelectedItems.getFirst())) + { + UpdateFileItem* fileItem(static_cast<UpdateFileItem*>(listSelectedItems.getFirst())); + tmpFileName = fileItem->filePath(); + tmpRevision = fileItem->entry().m_revision; + } + + *filename = tmpFileName; + if (revision) + *revision = tmpRevision; +} + + +QStringList UpdateView::multipleSelection() const +{ + QStringList res; + + const QPtrList<QListViewItem>& listSelectedItems(selectedItems()); + for (QPtrListIterator<QListViewItem> it(listSelectedItems); + it.current() != 0; ++it) + { + if ((*it)->isVisible()) + res.append(static_cast<UpdateItem*>(*it)->filePath()); + } + + return res; +} + + +QStringList UpdateView::fileSelection() const +{ + QStringList res; + + const QPtrList<QListViewItem>& listSelectedItems(selectedItems()); + for (QPtrListIterator<QListViewItem> it(listSelectedItems); + it.current() != 0; ++it) + { + QListViewItem* item(*it); + + if (isFileItem(item) && item->isVisible()) + res.append(static_cast<UpdateFileItem*>(item)->filePath()); + } + + return res; +} + + +const QColor& UpdateView::conflictColor() const +{ + return m_conflictColor; +} + + +const QColor& UpdateView::localChangeColor() const +{ + return m_localChangeColor; +} + + +const QColor& UpdateView::remoteChangeColor() const +{ + return m_remoteChangeColor; +} + + +const QColor& UpdateView::notInCvsColor() const +{ + return m_notInCvsColor; +} + + +bool UpdateView::isUnfoldingTree() const +{ + return m_unfoldingTree; +} + + +// updates internal data +void UpdateView::replaceItem(QListViewItem* oldItem, + QListViewItem* newItem) +{ + const int index(relevantSelection.find(oldItem)); + if (index >= 0) + relevantSelection.replace(index, newItem); +} + + +void UpdateView::unfoldSelectedFolders() +{ + QApplication::setOverrideCursor(waitCursor); + + int previousDepth = 0; + bool isUnfolded = false; + + QStringList selection = multipleSelection(); + + // setup name of selected folder + QString selectedItem = selection.first(); + if( selectedItem.contains('/') ) + selectedItem.remove(0, selectedItem.findRev('/')+1); + + // avoid flicker + const bool updatesEnabled = isUpdatesEnabled(); + setUpdatesEnabled(false); + + QListViewItemIterator it(this); + while( QListViewItem* item = it.current() ) + { + if( isDirItem(item) ) + { + UpdateDirItem* dirItem = static_cast<UpdateDirItem*>(item); + + // below selected folder? + if( previousDepth && dirItem->depth() > previousDepth ) + { + // if this dir wasn't scanned already scan it recursive + // (this is only a hack to reduce the processEvents() calls, + // setOpen() would scan the dir too) + if (dirItem->wasScanned() == false) + { + const bool recursive = true; + dirItem->maybeScanDir(recursive); + + // scanning can take some time so keep the gui alive + qApp->processEvents(); + } + + dirItem->setOpen(!isUnfolded); + } + // selected folder? + else if( selectedItem == dirItem->entry().m_name ) + { + previousDepth = dirItem->depth(); + isUnfolded = dirItem->isOpen(); + + // if this dir wasn't scanned already scan it recursive + // (this is only a hack to reduce the processEvents() calls, + // setOpen() would scan the dir too) + if (dirItem->wasScanned() == false) + { + const bool recursive = true; + dirItem->maybeScanDir(recursive); + + // scanning can take some time so keep the gui alive + qApp->processEvents(); + } + + dirItem->setOpen(!isUnfolded); + } + // back to the level of the selected folder or above? + else if( previousDepth && dirItem->depth() >= previousDepth ) + { + previousDepth = 0; + } + } + + ++it; + } + + // maybe some UpdateDirItem was opened the first time so check the whole tree + setFilter(filter()); + + setUpdatesEnabled(updatesEnabled); + triggerUpdate(); + + QApplication::restoreOverrideCursor(); +} + + +void UpdateView::unfoldTree() +{ + QApplication::setOverrideCursor(waitCursor); + + m_unfoldingTree = true; + + const bool updatesEnabled(isUpdatesEnabled()); + + setUpdatesEnabled(false); + + QListViewItemIterator it(this); + while (QListViewItem* item = it.current()) + { + if (isDirItem(item)) + { + UpdateDirItem* dirItem(static_cast<UpdateDirItem*>(item)); + + // if this dir wasn't scanned already scan it recursive + // (this is only a hack to reduce the processEvents() calls, + // setOpen() would scan the dir too) + if (dirItem->wasScanned() == false) + { + const bool recursive(true); + dirItem->maybeScanDir(recursive); + + // scanning can take some time so keep the gui alive + qApp->processEvents(); + } + + dirItem->setOpen(true); + } + + ++it; + } + + // maybe some UpdateDirItem was opened the first time so check the whole tree + setFilter(filter()); + + setUpdatesEnabled(updatesEnabled); + + triggerUpdate(); + + m_unfoldingTree = false; + + QApplication::restoreOverrideCursor(); +} + + +void UpdateView::foldTree() +{ + QListViewItemIterator it(this); + while (QListViewItem* item = it.current()) + { + // don't close the top level directory + if (isDirItem(item) && item->parent()) + item->setOpen(false); + + ++it; + } +} + + +/** + * Clear the tree view and insert the directory dirname + * into it as the new root item + */ +void UpdateView::openDirectory(const QString& dirName) +{ + clear(); + + // do this each time as the configuration could be changed + updateColors(); + + Entry entry; + entry.m_name = dirName; + entry.m_type = Entry::Dir; + + UpdateDirItem *item = new UpdateDirItem(this, entry); + item->setOpen(true); + setCurrentItem(item); + setSelected(item, true); +} + + +/** + * Start a job. We want to be able to change the status field + * correctly afterwards, so we have to remember the current + * selection (which the user may change during the update). + * In the recursive case, we collect all relevant directories. + * Furthermore, we have to change the items to undefined state. + */ +void UpdateView::prepareJob(bool recursive, Action action) +{ + act = action; + + // Scan recursively all entries - there's no way around this here + if (recursive) + static_cast<UpdateDirItem*>(firstChild())->maybeScanDir(true); + + rememberSelection(recursive); + if (act != Add) + markUpdated(false, false); +} + + +/** + * Finishes a job. What we do depends a bit on + * whether the command was successful or not. + */ +void UpdateView::finishJob(bool normalExit, int exitStatus) +{ + // cvs exitStatus == 1 only means that there're conflicts + const bool success(normalExit && (exitStatus == 0 || exitStatus == 1)); + if (act != Add) + markUpdated(true, success); + syncSelection(); + + // maybe some new items were created or + // visibility of items changed so check the whole tree + setFilter(filter()); +} + + +/** + * Marking non-selected items in a directory updated (as a consequence + * of not appearing in 'cvs update' output) is done in two steps: In the + * first, they are marked as 'indefinite', so that their status on the screen + * isn't misrepresented. In the second step, they are either set + * to 'UpToDate' (success=true) or 'Unknown'. + */ +void UpdateView::markUpdated(bool laststage, bool success) +{ + QPtrListIterator<QListViewItem> it(relevantSelection); + for ( ; it.current(); ++it) + if (isDirItem(it.current())) + { + for (QListViewItem *item = it.current()->firstChild(); item; + item = item->nextSibling() ) + if (isFileItem(item)) + { + UpdateFileItem* fileItem = static_cast<UpdateFileItem*>(item); + fileItem->markUpdated(laststage, success); + } + } + else + { + UpdateFileItem* fileItem = static_cast<UpdateFileItem*>(it.current()); + fileItem->markUpdated(laststage, success); + } +} + + +/** + * Remember the selection, see prepareJob() + */ +void UpdateView::rememberSelection(bool recursive) +{ + std::set<QListViewItem*> setItems; + for (QListViewItemIterator it(this); it.current(); ++it) + { + QListViewItem* item(it.current()); + + // if this item is selected and if it was not inserted already + // and if we work recursive and if it is a dir item then insert + // all sub dirs + // DON'T CHANGE TESTING ORDER + if (item->isSelected() + && setItems.insert(item).second + && recursive + && isDirItem(item)) + { + QPtrStack<QListViewItem> s; + for (QListViewItem* childItem = item->firstChild(); childItem; + childItem = childItem->nextSibling() ? childItem->nextSibling() : s.pop()) + { + // if this item is a dir item and if it is was not + // inserted already then insert all sub dirs + // DON'T CHANGE TESTING ORDER + if (isDirItem(childItem) && setItems.insert(childItem).second) + { + if (QListViewItem* childChildItem = childItem->firstChild()) + s.push(childChildItem); + } + } + } + } + + // Copy the set to the list + relevantSelection.clear(); + std::set<QListViewItem*>::const_iterator const itItemEnd = setItems.end(); + for (std::set<QListViewItem*>::const_iterator itItem = setItems.begin(); + itItem != itItemEnd; ++itItem) + relevantSelection.append(*itItem); + +#if 0 + DEBUGOUT("Relevant:"); + QPtrListIterator<QListViewItem> it44(relevantSelection); + for (; it44.current(); ++it44) + DEBUGOUT(" " << (*it44)->text(UpdateFileItem::File)); + DEBUGOUT("End"); +#endif +} + + +/** + * Use the remembered selection to resynchronize + * with the actual directory and Entries content. + */ +void UpdateView::syncSelection() +{ + // compute all directories which are selected or contain a selected file + // (in recursive mode this includes all sub directories) + std::set<UpdateDirItem*> setDirItems; + for (QPtrListIterator<QListViewItem> itItem(relevantSelection); + itItem.current(); ++itItem) + { + QListViewItem* item(itItem.current()); + + UpdateDirItem* dirItem(0); + if (isDirItem(item)) + dirItem = static_cast<UpdateDirItem*>(item); + else if (QListViewItem* parentItem = item->parent()) + dirItem = static_cast<UpdateDirItem*>(parentItem); + + if (dirItem) + setDirItems.insert(dirItem); + } + + QApplication::setOverrideCursor(waitCursor); + + std::set<UpdateDirItem*>::const_iterator const itDirItemEnd = setDirItems.end(); + for (std::set<UpdateDirItem*>::const_iterator itDirItem = setDirItems.begin(); + itDirItem != itDirItemEnd; ++itDirItem) + { + UpdateDirItem* dirItem = *itDirItem; + + dirItem->syncWithDirectory(); + dirItem->syncWithEntries(); + + qApp->processEvents(); + } + + QApplication::restoreOverrideCursor(); +} + + +/** + * Get the colors from the configuration each time the list view items + * are created. + */ +void UpdateView::updateColors() +{ + KConfigGroupSaver cs(&m_partConfig, "Colors"); + m_partConfig.setGroup("Colors"); + + QColor defaultColor = QColor(255, 130, 130); + m_conflictColor = m_partConfig.readColorEntry("Conflict", &defaultColor); + + defaultColor = QColor(130, 130, 255); + m_localChangeColor = m_partConfig.readColorEntry("LocalChange", &defaultColor); + + defaultColor = QColor(70, 210, 70); + m_remoteChangeColor = m_partConfig.readColorEntry("RemoteChange", &defaultColor); + + m_notInCvsColor = CervisiaSettings::notInCvsColor(); +} + + +/** + * Process one line from the output of 'cvs update'. If parseAsStatus + * is true, it is assumed that the output is from a command + * 'cvs update -n', i.e. cvs actually changes no files. + */ +void UpdateView::processUpdateLine(QString str) +{ + if (str.length() > 2 && str[1] == ' ') + { + EntryStatus status(Cervisia::Unknown); + switch (str[0].latin1()) + { + case 'C': + status = Cervisia::Conflict; + break; + case 'A': + status = Cervisia::LocallyAdded; + break; + case 'R': + status = Cervisia::LocallyRemoved; + break; + case 'M': + status = Cervisia::LocallyModified; + break; + case 'U': + status = (act == UpdateNoAct) ? Cervisia::NeedsUpdate : Cervisia::Updated; + break; + case 'P': + status = (act == UpdateNoAct) ? Cervisia::NeedsPatch : Cervisia::Patched; + break; + case '?': + status = Cervisia::NotInCVS; + break; + default: + return; + } + updateItem(str.mid(2), status, false); + } + + const QString removedFileStart(QString::fromLatin1("cvs server: ")); + const QString removedFileEnd(QString::fromLatin1(" is no longer in the repository")); + if (str.startsWith(removedFileStart) && str.endsWith(removedFileEnd)) + { + } + +#if 0 + else if (str.left(21) == "cvs server: Updating " || + str.left(21) == "cvs update: Updating ") + updateItem(str.right(str.length()-21), Unknown, true); +#endif +} + + +void UpdateView::updateItem(const QString& filePath, EntryStatus status, bool isdir) +{ + if (isdir && filePath == QChar('.')) + return; + + const QFileInfo fileInfo(filePath); + + UpdateDirItem* rootItem = static_cast<UpdateDirItem*>(firstChild()); + UpdateDirItem* dirItem = findOrCreateDirItem(fileInfo.dirPath(), rootItem); + + dirItem->updateChildItem(fileInfo.fileName(), status, isdir); +} + + +void UpdateView::itemExecuted(QListViewItem *item) +{ + if (isFileItem(item)) + emit fileOpened(static_cast<UpdateFileItem*>(item)->filePath()); +} + + +#include "updateview.moc" + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/updateview.h b/cervisia/updateview.h new file mode 100644 index 00000000..d01446a9 --- /dev/null +++ b/cervisia/updateview.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann <bernd@mail.berlios.de> + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef UPDATEVIEW_H +#define UPDATEVIEW_H + + +#include <klistview.h> + +#include <qptrlist.h> + +#include "entry.h" + + +class KConfig; + + +class UpdateView : public KListView +{ + Q_OBJECT + +public: + + enum Filter { NoFilter=0, OnlyDirectories=1, NoUpToDate=2, + NoRemoved=4, NoNotInCVS=8 , NoEmptyDirectories = 16 }; + enum Action { Add, Remove, Update, UpdateNoAct, Commit }; + + explicit UpdateView(KConfig& partConfig, QWidget *parent=0, const char *name=0); + + virtual ~UpdateView(); + + void setFilter(Filter filter); + Filter filter() const; + + bool hasSingleSelection() const; + void getSingleSelection(QString *filename, QString *revision=0) const; + /* Returns a list of all marked files and directories */ + QStringList multipleSelection() const; + /* Returns a list of all marked files, excluding directories*/ + QStringList fileSelection() const; + + void openDirectory(const QString& dirname); + void prepareJob(bool recursive, Action action); + + const QColor& conflictColor() const; + const QColor& localChangeColor() const; + const QColor& remoteChangeColor() const; + const QColor& notInCvsColor() const; + + /** + * @return \c true iff unfoldTree() is active. + */ + bool isUnfoldingTree() const; + + void replaceItem(QListViewItem*, QListViewItem*); + +signals: + void fileOpened(QString filename); + +public slots: + void unfoldSelectedFolders(); + void unfoldTree(); + void foldTree(); + void finishJob(bool normalExit, int exitStatus); + void processUpdateLine(QString line); + +private slots: + void itemExecuted(QListViewItem *item); + +private: + void updateItem(const QString &filename, Cervisia::EntryStatus status, bool isdir); + void rememberSelection(bool recursive); + void syncSelection(); + void markUpdated(bool laststage, bool success); + + void updateColors(); + + KConfig& m_partConfig; + + Filter filt; + Action act; + QPtrList<QListViewItem> relevantSelection; + + QColor m_conflictColor; + QColor m_localChangeColor; + QColor m_remoteChangeColor; + QColor m_notInCvsColor; + + /** + * \c true iff unfoldTree() is active (is needed by UpdateDirItem::setOpen()). + */ + bool m_unfoldingTree; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/updateview_items.cpp b/cervisia/updateview_items.cpp new file mode 100644 index 00000000..76223c24 --- /dev/null +++ b/cervisia/updateview_items.cpp @@ -0,0 +1,826 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann <bernd@mail.berlios.de> + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "updateview_items.h" + +#include <cassert> + +#include <qdir.h> +#include <qpainter.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kglobalsettings.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kmimetype.h> + +#include "cvsdir.h" +#include "entry.h" +#include "misc.h" +#include "updateview_visitors.h" + + +using Cervisia::Entry; +using Cervisia::EntryStatus; + + +// ------------------------------------------------------------------------------ +// UpdateItem +// ------------------------------------------------------------------------------ + + +QString UpdateItem::dirPath() const +{ + QString path; + + const UpdateItem* item = static_cast<UpdateItem*>(parent()); + while (item) + { + const UpdateItem* parentItem = static_cast<UpdateItem*>(item->parent()); + if (parentItem) + { + path.prepend(item->m_entry.m_name + QDir::separator()); + } + + item = parentItem; + } + + return path; +} + + +QString UpdateItem::filePath() const +{ + // the filePath of the root item is '.' + return parent() ? dirPath() + m_entry.m_name : QChar('.'); +} + + +// ------------------------------------------------------------------------------ +// UpdateDirItem +// ------------------------------------------------------------------------------ + + +UpdateDirItem::UpdateDirItem(UpdateDirItem* parent, + const Entry& entry) + : UpdateItem(parent, entry), + m_opened(false) +{ + setExpandable(true); + setPixmap(0, SmallIcon("folder")); +} + + +UpdateDirItem::UpdateDirItem(UpdateView* parent, + const Entry& entry) + : UpdateItem(parent, entry), + m_opened(false) +{ + setExpandable(true); + setPixmap(0, SmallIcon("folder")); +} + + +/** + * Update the status of an item; if it doesn't exist yet, create new one + */ +void UpdateDirItem::updateChildItem(const QString& name, + EntryStatus status, + bool isdir) +{ + if (UpdateItem* item = findItem(name)) + { + if (isFileItem(item)) + { + UpdateFileItem* fileItem = static_cast<UpdateFileItem*>(item); + fileItem->setStatus(status); + } + return; + } + + // Not found, make new entry + Entry entry; + entry.m_name = name; + if (isdir) + { + entry.m_type = Entry::Dir; + createDirItem(entry)->maybeScanDir(true); + } + else + { + entry.m_type = Entry::File; + createFileItem(entry)->setStatus(status); + } +} + + +/** + * Update the revision and tag of an item. Use status only to create + * new items and for items which were NotInCVS. + */ +void UpdateDirItem::updateEntriesItem(const Entry& entry, + bool isBinary) +{ + if (UpdateItem* item = findItem(entry.m_name)) + { + if (isFileItem(item)) + { + UpdateFileItem* fileItem = static_cast<UpdateFileItem*>(item); + if (fileItem->entry().m_status == Cervisia::NotInCVS || + fileItem->entry().m_status == Cervisia::LocallyRemoved || + entry.m_status == Cervisia::LocallyAdded || + entry.m_status == Cervisia::LocallyRemoved || + entry.m_status == Cervisia::Conflict) + { + fileItem->setStatus(entry.m_status); + } + fileItem->setRevTag(entry.m_revision, entry.m_tag); + fileItem->setDate(entry.m_dateTime); + fileItem->setPixmap(0, isBinary ? SmallIcon("binary") : QPixmap()); + } + return; + } + + // Not found, make new entry + if (entry.m_type == Entry::Dir) + createDirItem(entry)->maybeScanDir(true); + else + createFileItem(entry); +} + + +void UpdateDirItem::scanDirectory() +{ + const QString& path(filePath()); + if (!QFile::exists(path)) + return; + + const CvsDir dir(path); + + const QFileInfoList *files = dir.entryInfoList(); + if (files) + { + QFileInfoListIterator it(*files); + for (; it.current(); ++it) + { + Entry entry; + entry.m_name = it.current()->fileName(); + if (it.current()->isDir()) + { + entry.m_type = Entry::Dir; + createDirItem(entry); + } + else + { + entry.m_type = Entry::File; + entry.m_status = Cervisia::NotInCVS; + createFileItem(entry); + } + } + } +} + + +UpdateDirItem* UpdateDirItem::createDirItem(const Entry& entry) +{ + UpdateItem* item(insertItem(new UpdateDirItem(this, entry))); + assert(isDirItem(item)); + return static_cast<UpdateDirItem*>(item); +} + + +UpdateFileItem* UpdateDirItem::createFileItem(const Entry& entry) +{ + UpdateItem* item(insertItem(new UpdateFileItem(this, entry))); + assert(isFileItem(item)); + return static_cast<UpdateFileItem*>(item); +} + + +UpdateItem* UpdateDirItem::insertItem(UpdateItem* item) +{ + QPair<TMapItemsByName::iterator, bool> result + = m_itemsByName.insert(TMapItemsByName::value_type(item->entry().m_name, item)); + if (!result.second) + { + // OK, an item with that name already exists. If the item type is the + // same then keep the old one to preserve it's status information + UpdateItem* existingItem = *result.first; + if (existingItem->rtti() == item->rtti()) + { + delete item; + item = existingItem; + } + else + { + // avoid dangling pointers in the view + updateView()->replaceItem(existingItem, item); + + delete existingItem; + *result.first = item; + } + } + + return item; +} + + +UpdateItem* UpdateDirItem::findItem(const QString& name) const +{ + const TMapItemsByName::const_iterator it = m_itemsByName.find(name); + + return (it != m_itemsByName.end()) ? *it : 0; +} + +// Qt-3.3.8 changed the parsing in QDateTime::fromString() but introduced +// a bug which leads to the problem that days with 1 digit will incorrectly being +// parsed as day 0 - which is invalid. +// workaround with the implementation from Qt-3.3.6 +QDateTime parseDateTime(const QString &s) +{ + static const char * const qt_shortMonthNames[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + + QString monthName( s.mid( 4, 3 ) ); + int month = -1; + // Assume that English monthnames are the default + for ( int i = 0; i < 12; ++i ) { + if ( monthName == qt_shortMonthNames[i] ) { + month = i + 1; + break; + } + } + // If English names can't be found, search the localized ones + if ( month == -1 ) { + for ( int i = 1; i <= 12; ++i ) { + if ( monthName == QDate::shortMonthName( i ) ) { + month = i; + break; + } + } + } + if ( month < 1 || month > 12 ) { + qWarning( "QDateTime::fromString: Parameter out of range" ); + QDateTime dt; + return dt; + } + int day = s.mid( 8, 2 ).simplifyWhiteSpace().toInt(); + int year = s.right( 4 ).toInt(); + QDate date( year, month, day ); + QTime time; + int hour, minute, second; + int pivot = s.find( QRegExp(QString::fromLatin1("[0-9][0-9]:[0-9][0-9]:[0-9][0-9]")) ); + if ( pivot != -1 ) { + hour = s.mid( pivot, 2 ).toInt(); + minute = s.mid( pivot+3, 2 ).toInt(); + second = s.mid( pivot+6, 2 ).toInt(); + time.setHMS( hour, minute, second ); + } + return QDateTime( date, time ); +} + +// Format of the CVS/Entries file: +// /NAME/REVISION/[CONFLICT+]TIMESTAMP/OPTIONS/TAGDATE + +void UpdateDirItem::syncWithEntries() +{ + const QString path(filePath() + QDir::separator()); + + QFile f(path + "CVS/Entries"); + if( f.open(IO_ReadOnly) ) + { + QTextStream stream(&f); + while( !stream.eof() ) + { + QString line = stream.readLine(); + + Cervisia::Entry entry; + + const bool isDir(line[0] == 'D'); + + if( isDir ) + line.remove(0, 1); + + if( line[0] != '/' ) + continue; + + entry.m_type = isDir ? Entry::Dir : Entry::File; + entry.m_name = line.section('/', 1, 1); + + if (isDir) + { + updateEntriesItem(entry, false); + } + else + { + QString rev(line.section('/', 2, 2)); + const QString timestamp(line.section('/', 3, 3)); + const QString options(line.section('/', 4, 4)); + entry.m_tag = line.section('/', 5, 5); + + const bool isBinary(options.find("-kb") >= 0); + + // file date in local time + entry.m_dateTime = QFileInfo(path + entry.m_name).lastModified(); + + if( rev == "0" ) + entry.m_status = Cervisia::LocallyAdded; + else if( rev.length() > 2 && rev[0] == '-' ) + { + entry.m_status = Cervisia::LocallyRemoved; + rev.remove(0, 1); + } + else if (timestamp.find('+') >= 0) + { + entry.m_status = Cervisia::Conflict; + } + else + { + // workaround Qt-3.3.8 bug with our own function (see function above) + // const QDateTime date(QDateTime::fromString(timestamp)); // UTC Time + const QDateTime date(parseDateTime(timestamp)); // UTC Time + QDateTime fileDateUTC; + fileDateUTC.setTime_t(entry.m_dateTime.toTime_t(), Qt::UTC); + if (date != fileDateUTC) + entry.m_status = Cervisia::LocallyModified; + } + + entry.m_revision = rev; + + updateEntriesItem(entry, isBinary); + } + } + } +} + + +/** + * Test if files was removed from repository. + */ +void UpdateDirItem::syncWithDirectory() +{ + QDir dir(filePath()); + + for (TMapItemsByName::iterator it(m_itemsByName.begin()), + itEnd(m_itemsByName.end()); + it != itEnd; ++it) + { + // only files + if (isFileItem(*it)) + { + UpdateFileItem* fileItem = static_cast<UpdateFileItem*>(*it); + + // is file removed? + if (!dir.exists(it.key())) + { + fileItem->setStatus(Cervisia::Removed); + fileItem->setRevTag(QString::null, QString::null); + } + } + } +} + + +/** + * Read in the content of the directory. If recursive is false, this + * is shallow, otherwise all child directories are scanned recursively. + */ +void UpdateDirItem::maybeScanDir(bool recursive) +{ + if (!m_opened) + { + m_opened = true; + scanDirectory(); + syncWithEntries(); + + // sort the created items + sort(); + } + + if (recursive) + { + for (TMapItemsByName::iterator it(m_itemsByName.begin()), + itEnd(m_itemsByName.end()); + it != itEnd; ++it) + { + if (isDirItem(*it)) + static_cast<UpdateDirItem*>(*it)->maybeScanDir(true); + } + } +} + + +void UpdateDirItem::accept(Visitor& visitor) +{ + visitor.preVisit(this); + + for (TMapItemsByName::iterator it(m_itemsByName.begin()), + itEnd(m_itemsByName.end()); + it != itEnd; ++it) + { + (*it)->accept(visitor); + } + + visitor.postVisit(this); +} + + +void UpdateDirItem::setOpen(bool open) +{ + if ( open ) + { + const bool openFirstTime(!wasScanned()); + + maybeScanDir(false); + + // if new items were created their visibility must be checked + // (not while unfoldTree() as this could be slow and unfoldTree() + // calls setFilter() itself) + UpdateView* view = updateView(); + if (openFirstTime && !view->isUnfoldingTree()) + view->setFilter(view->filter()); + } + + QListViewItem::setOpen(open); +} + + +int UpdateDirItem::compare(QListViewItem* i, + int /*column*/, + bool bAscending) const +{ + // UpdateDirItems are always lesser than UpdateFileItems + if (isFileItem(i)) + return bAscending ? -1 : 1; + + const UpdateDirItem* item(static_cast<UpdateDirItem*>(i)); + + // for every column just compare the directory name + return entry().m_name.localeAwareCompare(item->entry().m_name); +} + + +QString UpdateDirItem::text(int column) const +{ + QString result; + if (column == Name) + result = entry().m_name; + + return result; +} + + +// ------------------------------------------------------------------------------ +// UpdateFileItem +// ------------------------------------------------------------------------------ + + +UpdateFileItem::UpdateFileItem(UpdateDirItem* parent, const Entry& entry) + : UpdateItem(parent, entry), + m_undefined(false) +{ +} + + +void UpdateFileItem::setStatus(EntryStatus status) +{ + if (status != m_entry.m_status) + { + m_entry.m_status = status; + const bool visible(applyFilter(updateView()->filter())); + if (visible) + repaint(); + } + m_undefined = false; +} + + +void UpdateFileItem::accept(Visitor& visitor) +{ + visitor.visit(this); +} + + +bool UpdateFileItem::applyFilter(UpdateView::Filter filter) +{ + bool visible(true); + if (filter & UpdateView::OnlyDirectories) + visible = false; + + bool unmodified = (entry().m_status == Cervisia::UpToDate) || + (entry().m_status == Cervisia::Unknown); + if ((filter & UpdateView::NoUpToDate) && unmodified) + visible = false; + if ((filter & UpdateView::NoRemoved) && (entry().m_status == Cervisia::Removed)) + visible = false; + if ((filter & UpdateView::NoNotInCVS) && (entry().m_status == Cervisia::NotInCVS)) + visible = false; + + setVisible(visible); + + return visible; +} + + +void UpdateFileItem::setRevTag(const QString& rev, const QString& tag) +{ + m_entry.m_revision = rev; + + if (tag.length() == 20 && tag[0] == 'D' && tag[5] == '.' + && tag[8] == '.' && tag[11] == '.' && tag[14] == '.' + && tag[17] == '.') + { + const QDate tagDate(tag.mid(1, 4).toInt(), + tag.mid(6, 2).toInt(), + tag.mid(9, 2).toInt()); + const QTime tagTime(tag.mid(12, 2).toInt(), + tag.mid(15, 2).toInt(), + tag.mid(18, 2).toInt()); + const QDateTime tagDateTimeUtc(tagDate, tagTime); + + if (tagDateTimeUtc.isValid()) + { + // This is in UTC and must be converted to local time. + // + // A bit strange but I didn't find anything easier which is portable. + // Compute the difference between UTC and local timezone for this + // tag date. + const unsigned int dateTimeInSeconds(tagDateTimeUtc.toTime_t()); + QDateTime dateTime; + dateTime.setTime_t(dateTimeInSeconds, Qt::UTC); + const int localUtcOffset(dateTime.secsTo(tagDateTimeUtc)); + + const QDateTime tagDateTimeLocal(tagDateTimeUtc.addSecs(localUtcOffset)); + + m_entry.m_tag = KGlobal::locale()->formatDateTime(tagDateTimeLocal); + } + else + m_entry.m_tag = tag; + } + else if (tag.length() > 1 && tag[0] == 'T') + m_entry.m_tag = tag.mid(1); + else + m_entry.m_tag = tag; + + if (isVisible()) + { + widthChanged(); + repaint(); + } +} + + +void UpdateFileItem::setDate(const QDateTime& date) +{ + m_entry.m_dateTime = date; +} + + +void UpdateFileItem::markUpdated(bool laststage, + bool success) +{ + EntryStatus newstatus = m_entry.m_status; + + if (laststage) + { + if (undefinedState() && m_entry.m_status != Cervisia::NotInCVS) + newstatus = success? Cervisia::UpToDate : Cervisia::Unknown; + setStatus(newstatus); + } + else + setUndefinedState(true); +} + + +int UpdateFileItem::statusClass() const +{ + int iResult(0); + switch (entry().m_status) + { + case Cervisia::Conflict: + iResult = 0; + break; + case Cervisia::LocallyAdded: + iResult = 1; + break; + case Cervisia::LocallyRemoved: + iResult = 2; + break; + case Cervisia::LocallyModified: + iResult = 3; + break; + case Cervisia::Updated: + case Cervisia::NeedsUpdate: + case Cervisia::Patched: + case Cervisia::Removed: + case Cervisia::NeedsPatch: + case Cervisia::NeedsMerge: + iResult = 4; + break; + case Cervisia::NotInCVS: + iResult = 5; + break; + case Cervisia::UpToDate: + case Cervisia::Unknown: + iResult = 6; + break; + } + + return iResult; +} + + +int UpdateFileItem::compare(QListViewItem* i, + int column, + bool bAscending) const +{ + // UpdateDirItems are always lesser than UpdateFileItems + if (isDirItem(i)) + return bAscending ? 1 : -1; + + const UpdateFileItem* item = static_cast<UpdateFileItem*>(i); + + int iResult(0); + switch (column) + { + case Name: + iResult = entry().m_name.localeAwareCompare(item->entry().m_name); + break; + case MimeType: + iResult = KMimeType::findByPath(entry().m_name)->comment().localeAwareCompare(KMimeType::findByPath(item->entry().m_name)->comment()); + break; + case Status: + if ((iResult = ::compare(statusClass(), item->statusClass())) == 0) + iResult = entry().m_name.localeAwareCompare(item->entry().m_name); + break; + case Revision: + iResult = ::compareRevisions(entry().m_revision, item->entry().m_revision); + break; + case TagOrDate: + iResult = entry().m_tag.localeAwareCompare(item->entry().m_tag); + break; + case Timestamp: + iResult = ::compare(entry().m_dateTime, item->entry().m_dateTime); + break; + } + + return iResult; +} + + +QString UpdateFileItem::text(int column) const +{ + QString result; + switch (column) + { + case Name: + result = entry().m_name; + break; + case MimeType: + result = KMimeType::findByPath(entry().m_name)->comment(); + break; + case Status: + result = toString(entry().m_status); + break; + case Revision: + result = entry().m_revision; + break; + case TagOrDate: + result = entry().m_tag; + break; + case Timestamp: + if (entry().m_dateTime.isValid()) + result = KGlobal::locale()->formatDateTime(entry().m_dateTime); + break; + } + + return result; +} + + +void UpdateFileItem::paintCell(QPainter *p, + const QColorGroup &cg, + int col, + int width, + int align) +{ + const UpdateView* view(updateView()); + + QColor color; + switch (m_entry.m_status) + { + case Cervisia::Conflict: + color = view->conflictColor(); + break; + case Cervisia::LocallyAdded: + case Cervisia::LocallyModified: + case Cervisia::LocallyRemoved: + color = view->localChangeColor(); + break; + case Cervisia::NeedsMerge: + case Cervisia::NeedsPatch: + case Cervisia::NeedsUpdate: + case Cervisia::Patched: + case Cervisia::Removed: + case Cervisia::Updated: + color = view->remoteChangeColor(); + break; + case Cervisia::NotInCVS: + color = view->notInCvsColor(); + break; + case Cervisia::Unknown: + case Cervisia::UpToDate: + break; + } + + const QFont oldFont(p->font()); + QColorGroup mycg(cg); + if (color.isValid() && color != KGlobalSettings::textColor()) + { + QFont myFont(oldFont); + myFont.setBold(true); + p->setFont(myFont); + mycg.setColor(QColorGroup::Text, color); + } + + QListViewItem::paintCell(p, mycg, col, width, align); + + if (color.isValid()) + { + p->setFont(oldFont); + } +} + + +/** + * Finds or creates the UpdateDirItem with path \a dirPath. If \a dirPath + * is "." \a rootItem is returned. + */ +UpdateDirItem* findOrCreateDirItem(const QString& dirPath, + UpdateDirItem* rootItem) +{ + assert(!dirPath.isEmpty()); + assert(rootItem); + + UpdateDirItem* dirItem(rootItem); + + if (dirPath != QChar('.')) + { + const QStringList& dirNames(QStringList::split('/', dirPath)); + const QStringList::const_iterator itDirNameEnd(dirNames.end()); + for (QStringList::const_iterator itDirName(dirNames.begin()); + itDirName != itDirNameEnd; ++itDirName) + { + const QString& dirName(*itDirName); + + UpdateItem* item = dirItem->findItem(dirName); + if (isFileItem(item)) + { + // this happens if you + // - add a directory outside of Cervisia + // - update status (a file item is created for the directory) + // - add new directory in Cervisia + // - update status + kdDebug(8050) << "findOrCreateDirItem(): file changed to dir " << dirName << endl; + + // just create a new dir item, createDirItem() will delete the + // file item and update the m_itemsByName map + item = 0; + } + + if (!item) + { + kdDebug(8050) << "findOrCreateDirItem(): create dir item " << dirName << endl; + Entry entry; + entry.m_name = dirName; + entry.m_type = Entry::Dir; + item = dirItem->createDirItem(entry); + } + + assert(isDirItem(item)); + + dirItem = static_cast<UpdateDirItem*>(item); + } + } + + return dirItem; +} diff --git a/cervisia/updateview_items.h b/cervisia/updateview_items.h new file mode 100644 index 00000000..49f5cf91 --- /dev/null +++ b/cervisia/updateview_items.h @@ -0,0 +1,169 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann <bernd@mail.berlios.de> + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef UPDATEVIEW_ITEMS_H +#define UPDATEVIEW_ITEMS_H + + +#include <qdatetime.h> +#include <qlistview.h> +#include <qmap.h> + +#include "entry.h" +#include "updateview.h" + + +class UpdateDirItem; +class UpdateFileItem; +class Visitor; + + +UpdateDirItem* findOrCreateDirItem(const QString&, UpdateDirItem*); + + +class UpdateItem : public QListViewItem +{ +public: + + UpdateItem(UpdateView* parent, const Cervisia::Entry& entry) + : QListViewItem(parent), m_entry(entry) {} + UpdateItem(UpdateItem* parent, const Cervisia::Entry& entry) + : QListViewItem(parent), m_entry(entry) {} + + const Cervisia::Entry& entry() const { return m_entry; } + + // Returns the path (relative to the repository). + // QString::null for the root item and its (direct) children. + // If it's not QString::null it ends with '/'. + QString dirPath() const; + + // Returns the file name, including the path (relative to the repository) + QString filePath() const; + + virtual void accept(Visitor&) = 0; + +protected: + + UpdateView* updateView() const { return static_cast<UpdateView*>(listView()); } + + Cervisia::Entry m_entry; +}; + + +class UpdateDirItem : public UpdateItem +{ +public: + + enum { Name }; + + UpdateDirItem(UpdateView* parent, const Cervisia::Entry& entry); + UpdateDirItem(UpdateDirItem* parent, const Cervisia::Entry& entry); + + void syncWithDirectory(); + void syncWithEntries(); + void updateChildItem(const QString& name, Cervisia::EntryStatus status, bool isdir); + void updateEntriesItem(const Cervisia::Entry& entry, bool isBinary); + + bool wasScanned() const { return m_opened; } + + virtual int compare(QListViewItem* i, int col, bool) const; + virtual QString text(int col) const; + virtual void setOpen(bool o); + virtual int rtti() const { return RTTI; } + + void maybeScanDir(bool recursive); + + virtual void accept(Visitor&); + + enum { RTTI = 10000 }; + +private: + + void scanDirectory(); + + UpdateDirItem* createDirItem(const Cervisia::Entry& entry); + UpdateFileItem* createFileItem(const Cervisia::Entry& entry); + + UpdateItem* insertItem(UpdateItem* item); + + UpdateItem* findItem(const QString& name) const; + + typedef QMap<QString, UpdateItem*> TMapItemsByName; + + TMapItemsByName m_itemsByName; + + bool m_opened; + + friend UpdateDirItem* findOrCreateDirItem(const QString&, UpdateDirItem*); +}; + + +class UpdateFileItem : public UpdateItem +{ +public: + + enum { Name, MimeType, Status, Revision, TagOrDate, Timestamp }; + + UpdateFileItem(UpdateDirItem* parent, const Cervisia::Entry& entry); + + bool undefinedState() const + { return m_undefined; } + + virtual int compare(QListViewItem* i, int col, bool) const; + virtual QString text(int col) const; + virtual void paintCell(QPainter *p, const QColorGroup &cg, + int col, int width, int align); + virtual int rtti() const { return RTTI; } + + void setStatus(Cervisia::EntryStatus status); + void setRevTag(const QString& rev, const QString& tag); + void setDate(const QDateTime& date); + void setUndefinedState(bool b) + { m_undefined = b; } + + void markUpdated(bool laststage, bool success); + + virtual void accept(Visitor&); + + bool applyFilter(UpdateView::Filter filter); + + enum { RTTI = 10001 }; + +private: + + int statusClass() const; + + bool m_undefined; +}; + + +inline bool isDirItem(const QListViewItem* item) +{ + return item && item->rtti() == UpdateDirItem::RTTI; +} + + +inline bool isFileItem(const QListViewItem* item) +{ + return item && item->rtti() == UpdateFileItem::RTTI; +} + + +#endif // UPDATEVIEW_ITEMS_H diff --git a/cervisia/updateview_visitors.cpp b/cervisia/updateview_visitors.cpp new file mode 100644 index 00000000..e18b5b68 --- /dev/null +++ b/cervisia/updateview_visitors.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "updateview_visitors.h" + +#include "updateview.h" +#include "updateview_items.h" + + +using Cervisia::Entry; + + +ApplyFilterVisitor::ApplyFilterVisitor(UpdateView::Filter filter) + : m_filter(filter) +{ +} + + +void ApplyFilterVisitor::preVisit(UpdateDirItem* item) +{ + // as QListViewItem::setVisible() is recursive we have to make + // this UpdateDirItem visible first and later we can make it invisible + item->setVisible(true); + + // assume that this item is invisible and correct it later + // (see markAllParentsAsVisible()) + m_invisibleDirItems.insert(item); +} + + +void ApplyFilterVisitor::postVisit(UpdateDirItem* item) +{ + // a UpdateDirItem is visible if + // - it has visible children + // - it is not opened + // - empty directories are not hidden + // - it has no parent (top level item) + const bool visible(!m_invisibleDirItems.count(item) + || !item->wasScanned() + || !(m_filter & UpdateView::NoEmptyDirectories) + || !item->parent()); + + // only set invisible as QListViewItem::setVisible() is recursive + // and so maybe overrides the state applied by the filter + if (visible) + { + markAllParentsAsVisible(item); + } + else + { + item->setVisible(false); + } +} + + +void ApplyFilterVisitor::visit(UpdateFileItem* item) +{ + const bool visible(item->applyFilter(m_filter)); + if (visible) + { + markAllParentsAsVisible(item); + } +} + + +void ApplyFilterVisitor::markAllParentsAsVisible(UpdateItem* item) +{ + while ((item = static_cast<UpdateDirItem*>(item->parent()))) + { + TItemSet::iterator it = m_invisibleDirItems.find(item); + if (it != m_invisibleDirItems.end()) + { + m_invisibleDirItems.erase(it); + } + else + { + // if this item isn't in the map anymore all parents + // are already removed too + break; + } + } +} diff --git a/cervisia/updateview_visitors.h b/cervisia/updateview_visitors.h new file mode 100644 index 00000000..c92fd615 --- /dev/null +++ b/cervisia/updateview_visitors.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2003-2007 André Wöbbeking <Woebbeking@kde.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef UPDATEVIEW_VISITORS_H +#define UPDATEVIEW_VISITORS_H + + +#include "updateview.h" + +#include <set> + + +class UpdateItem; +class UpdateDirItem; +class UpdateFileItem; + + +class Visitor +{ +public: + + virtual ~Visitor() {} + + virtual void preVisit(UpdateDirItem*) = 0; + virtual void postVisit(UpdateDirItem*) = 0; + + virtual void visit(UpdateFileItem*) = 0; +}; + + +class ApplyFilterVisitor : public Visitor +{ +public: + + explicit ApplyFilterVisitor(UpdateView::Filter filter); + + virtual void preVisit(UpdateDirItem*); + virtual void postVisit(UpdateDirItem*); + + virtual void visit(UpdateFileItem*); + +private: + + void markAllParentsAsVisible(UpdateItem*); + + UpdateView::Filter m_filter; + + typedef std::set<UpdateItem*> TItemSet; + TItemSet m_invisibleDirItems; +}; + + +#endif // UPDATEVIEW_VISITORS_H diff --git a/cervisia/version.h b/cervisia/version.h new file mode 100644 index 00000000..a1fc5bf0 --- /dev/null +++ b/cervisia/version.h @@ -0,0 +1,2 @@ +#define CERVISIA_VERSION "2.4.10" + diff --git a/cervisia/watchdlg.cpp b/cervisia/watchdlg.cpp new file mode 100644 index 00000000..317f5c95 --- /dev/null +++ b/cervisia/watchdlg.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "watchdlg.h" + +#include <qbuttongroup.h> +#include <qcheckbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qradiobutton.h> +#include <klocale.h> + + +WatchDialog::WatchDialog(ActionType action, QWidget *parent, const char *name) + : KDialogBase(parent, name, true, QString::null, + Ok | Cancel | Help, Ok, true) +{ + setCaption( (action==Add)? i18n("CVS Watch Add") : i18n("CVS Watch Remove") ); + + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout *layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + QLabel *textlabel = new QLabel + ( (action==Add)? i18n("Add watches for the following events:") + : i18n("Remove watches for the following events:"), mainWidget ); + layout->addWidget(textlabel, 0); + + all_button = new QRadioButton(i18n("&All"), mainWidget); + all_button->setFocus(); + all_button->setChecked(true); + layout->addWidget(all_button); + + only_button = new QRadioButton(i18n("&Only:"), mainWidget); + layout->addWidget(only_button); + + QGridLayout *eventslayout = new QGridLayout(layout); + eventslayout->addColSpacing(0, 20); + eventslayout->setColStretch(0, 0); + eventslayout->setColStretch(1, 1); + + commitbox = new QCheckBox(i18n("&Commits"), mainWidget); + commitbox->setEnabled(false); + eventslayout->addWidget(commitbox, 0, 1); + + editbox = new QCheckBox(i18n("&Edits"), mainWidget); + editbox->setEnabled(false); + eventslayout->addWidget(editbox, 1, 1); + + uneditbox = new QCheckBox(i18n("&Unedits"), mainWidget); + uneditbox->setEnabled(false); + eventslayout->addWidget(uneditbox, 2, 1); + + QButtonGroup* group = new QButtonGroup(mainWidget); + group->hide(); + group->insert(all_button); + group->insert(only_button); + + connect( only_button, SIGNAL(toggled(bool)), + commitbox, SLOT(setEnabled(bool)) ); + connect( only_button, SIGNAL(toggled(bool)), + editbox, SLOT(setEnabled(bool)) ); + connect( only_button, SIGNAL(toggled(bool)), + uneditbox, SLOT(setEnabled(bool)) ); + + setHelp("watches"); +} + + +WatchDialog::Events WatchDialog::events() const +{ + Events res = None; + if (all_button->isChecked()) + res = All; + else + { + if (commitbox->isChecked()) + res = Events(res | Commits); + if (editbox->isChecked()) + res = Events(res | Edits); + if (uneditbox->isChecked()) + res = Events(res | Unedits); + } + return res; +} + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/watchdlg.h b/cervisia/watchdlg.h new file mode 100644 index 00000000..59510dc2 --- /dev/null +++ b/cervisia/watchdlg.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 1999-2002 Bernd Gehrmann + * bernd@mail.berlios.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef WATCHDLG_H +#define WATCHDLG_H + + +#include <kdialogbase.h> + + +class QRadioButton; +class QCheckBox; + + +class WatchDialog : public KDialogBase +{ +public: + enum ActionType { Add, Remove }; + enum Events { None=0, All=1, Commits=2, Edits=4, Unedits=8 }; + + explicit WatchDialog( ActionType action, QWidget *parent=0, const char *name=0 ); + + Events events() const; + +private: + QRadioButton *all_button, *only_button; + QCheckBox *commitbox, *editbox, *uneditbox; +}; + +#endif + + +// Local Variables: +// c-basic-offset: 4 +// End: diff --git a/cervisia/watchersdlg.cpp b/cervisia/watchersdlg.cpp new file mode 100644 index 00000000..d7d849bd --- /dev/null +++ b/cervisia/watchersdlg.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include "watchersdlg.h" + +#include <qlayout.h> +#include <qtable.h> +#include <kconfig.h> +#include <klineedit.h> +#include <klocale.h> + +#include "misc.h" +#include "cvsservice_stub.h" +#include "progressdlg.h" + + +WatchersDialog::WatchersDialog(KConfig& cfg, QWidget* parent, const char* name) + : KDialogBase(parent, name, false, QString::null, + Close, ButtonCode(0), true) + , partConfig(cfg) +{ + QFrame* mainWidget = makeMainWidget(); + + QBoxLayout *layout = new QVBoxLayout(mainWidget, 0, spacingHint()); + + table = new QTable(mainWidget, "watchersTable"); + table->setNumCols(5); + table->setSelectionMode(QTable::NoSelection); + table->setColumnMovingEnabled(false); + table->setRowMovingEnabled(false); + table->setReadOnly(true); + table->setDragEnabled(false); + table->setSorting(true); + table->verticalHeader()->hide(); + table->setLeftMargin(0); + + QHeader* header = table->horizontalHeader(); + header->setLabel(0, i18n("File")); + header->setLabel(1, i18n("Watcher")); + header->setLabel(2, i18n("Edit")); + header->setLabel(3, i18n("Unedit")); + header->setLabel(4, i18n("Commit")); + + layout->addWidget(table, 1); + + setWFlags(Qt::WDestructiveClose | getWFlags()); + + QSize size = configDialogSize(partConfig, "WatchersDialog"); + resize(size); +} + + +WatchersDialog::~WatchersDialog() +{ + saveDialogSize(partConfig, "WatchersDialog"); +} + + +bool WatchersDialog::parseWatchers(CvsService_stub* cvsService, + const QStringList& files) +{ + setCaption(i18n("CVS Watchers")); + + DCOPRef job = cvsService->watchers(files); + if( !cvsService->ok() ) + return false; + + ProgressDialog dlg(this, "Watchers", job, "watchers", i18n("CVS Watchers")); + if( !dlg.execute() ) + return false; + + QString line; + int numRows = 0; + while( dlg.getLine(line) ) + { + // parse the output line + QStringList list = splitLine(line); + + // ignore empty lines and unknown files + if( list.isEmpty() || list[0] == "?" ) + continue; + + // add a new row to the table + table->setNumRows(numRows + 1); + + table->setText(numRows, 0, list[0]); + table->setText(numRows, 1, list[1]); + + QCheckTableItem* item = new QCheckTableItem(table, ""); + item->setChecked(list.contains("edit")); + table->setItem(numRows, 2, item); + + item = new QCheckTableItem(table, ""); + item->setChecked(list.contains("unedit")); + table->setItem(numRows, 3, item); + + item = new QCheckTableItem(table, ""); + item->setChecked(list.contains("commit")); + table->setItem(numRows, 4, item); + + ++numRows; + } + + return true; +} diff --git a/cervisia/watchersdlg.h b/cervisia/watchersdlg.h new file mode 100644 index 00000000..59b2d52c --- /dev/null +++ b/cervisia/watchersdlg.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef WATCHERSDLG_H +#define WATCHERSDLG_H + + +#include <kdialogbase.h> + +class QTable; +class KConfig; +class CvsService_stub; + + +class WatchersDialog : public KDialogBase +{ +public: + explicit WatchersDialog(KConfig& cfg, QWidget* parent = 0, + const char* name = 0); + virtual ~WatchersDialog(); + + bool parseWatchers(CvsService_stub* cvsService, const QStringList& files); + +private: + QTable* table; + KConfig& partConfig; +}; + +#endif |