summaryrefslogtreecommitdiffstats
path: root/cervisia
diff options
context:
space:
mode:
Diffstat (limited to 'cervisia')
-rw-r--r--cervisia/COPYING339
-rw-r--r--cervisia/ChangeLog1543
-rw-r--r--cervisia/HACKING79
-rw-r--r--cervisia/Makefile.am73
-rw-r--r--cervisia/README19
-rw-r--r--cervisia/TODO26
-rw-r--r--cervisia/addremovedlg.cpp101
-rw-r--r--cervisia/addremovedlg.h49
-rw-r--r--cervisia/addrepositorydlg.cpp202
-rw-r--r--cervisia/addrepositorydlg.h73
-rw-r--r--cervisia/annotatectl.cpp198
-rw-r--r--cervisia/annotatectl.h44
-rw-r--r--cervisia/annotatedlg.cpp58
-rw-r--r--cervisia/annotatedlg.h59
-rw-r--r--cervisia/annotateview.cpp206
-rw-r--r--cervisia/annotateview.h56
-rw-r--r--cervisia/cervisia-change_repos_list.pl59
-rw-r--r--cervisia/cervisia-normalize_cvsroot.pl53
-rw-r--r--cervisia/cervisia.1.in217
-rw-r--r--cervisia/cervisia.desktop74
-rw-r--r--cervisia/cervisia.pod109
-rw-r--r--cervisia/cervisia.upd92
-rw-r--r--cervisia/cervisiapart.cpp1939
-rw-r--r--cervisia/cervisiapart.h219
-rw-r--r--cervisia/cervisiapart.kcfg37
-rw-r--r--cervisia/cervisiasettings.kcfgc4
-rw-r--r--cervisia/cervisiashell.cpp227
-rw-r--r--cervisia/cervisiashell.h71
-rw-r--r--cervisia/cervisiashellui.rc19
-rw-r--r--cervisia/cervisiaui.rc179
-rw-r--r--cervisia/change_colors.pl31
-rw-r--r--cervisia/changelogdlg.cpp188
-rw-r--r--cervisia/changelogdlg.h60
-rw-r--r--cervisia/checkoutdlg.cpp484
-rw-r--r--cervisia/checkoutdlg.h89
-rw-r--r--cervisia/commitdlg.cpp326
-rw-r--r--cervisia/commitdlg.h86
-rw-r--r--cervisia/cvsdir.cpp56
-rw-r--r--cervisia/cvsdir.h44
-rw-r--r--cervisia/cvsinitdlg.cpp91
-rw-r--r--cervisia/cvsinitdlg.h52
-rw-r--r--cervisia/cvsservice/DESIGN108
-rw-r--r--cervisia/cvsservice/Makefile.am54
-rw-r--r--cervisia/cvsservice/TODO12
-rw-r--r--cervisia/cvsservice/cvsaskpass.cpp77
-rw-r--r--cervisia/cvsservice/cvsjob.cpp236
-rw-r--r--cervisia/cvsservice/cvsjob.h83
-rw-r--r--cervisia/cvsservice/cvsloginjob.cpp156
-rw-r--r--cervisia/cvsservice/cvsloginjob.h58
-rw-r--r--cervisia/cvsservice/cvsservice.cpp1008
-rw-r--r--cervisia/cvsservice/cvsservice.desktop73
-rw-r--r--cervisia/cvsservice/cvsservice.h375
-rw-r--r--cervisia/cvsservice/cvsserviceutils.cpp45
-rw-r--r--cervisia/cvsservice/cvsserviceutils.h40
-rw-r--r--cervisia/cvsservice/main.cpp46
-rw-r--r--cervisia/cvsservice/repository.cpp266
-rw-r--r--cervisia/cvsservice/repository.h109
-rw-r--r--cervisia/cvsservice/sshagent.cpp237
-rw-r--r--cervisia/cvsservice/sshagent.h64
-rw-r--r--cervisia/diffdlg.cpp509
-rw-r--r--cervisia/diffdlg.h87
-rw-r--r--cervisia/diffview.cpp507
-rw-r--r--cervisia/diffview.h132
-rw-r--r--cervisia/dirignorelist.cpp47
-rw-r--r--cervisia/dirignorelist.h50
-rw-r--r--cervisia/editwithmenu.cpp78
-rw-r--r--cervisia/editwithmenu.h55
-rw-r--r--cervisia/entry.cpp34
-rw-r--r--cervisia/entry.h85
-rw-r--r--cervisia/entry_status.cpp81
-rw-r--r--cervisia/entry_status.h64
-rw-r--r--cervisia/entry_status_change.h54
-rw-r--r--cervisia/eventsrc88
-rw-r--r--cervisia/globalignorelist.cpp100
-rw-r--r--cervisia/globalignorelist.h55
-rw-r--r--cervisia/hi16-app-cervisia.pngbin0 -> 963 bytes
-rw-r--r--cervisia/hi22-app-cervisia.pngbin0 -> 1210 bytes
-rw-r--r--cervisia/hi32-app-cervisia.pngbin0 -> 2300 bytes
-rw-r--r--cervisia/hi48-app-cervisia.pngbin0 -> 4074 bytes
-rw-r--r--cervisia/historydlg.cpp397
-rw-r--r--cervisia/historydlg.h62
-rw-r--r--cervisia/ignorelistbase.cpp49
-rw-r--r--cervisia/ignorelistbase.h49
-rw-r--r--cervisia/logdlg.cpp620
-rw-r--r--cervisia/logdlg.h105
-rw-r--r--cervisia/loginfo.cpp140
-rw-r--r--cervisia/loginfo.h163
-rw-r--r--cervisia/loglist.cpp233
-rw-r--r--cervisia/loglist.h71
-rw-r--r--cervisia/logmessageedit.cpp204
-rw-r--r--cervisia/logmessageedit.h61
-rw-r--r--cervisia/logplainview.cpp203
-rw-r--r--cervisia/logplainview.h62
-rw-r--r--cervisia/logtree.cpp501
-rw-r--r--cervisia/logtree.h91
-rw-r--r--cervisia/main.cpp213
-rw-r--r--cervisia/mergedlg.cpp164
-rw-r--r--cervisia/mergedlg.h65
-rw-r--r--cervisia/misc.cpp350
-rw-r--r--cervisia/misc.h98
-rw-r--r--cervisia/move_repositories.pl42
-rw-r--r--cervisia/overview.h15
-rw-r--r--cervisia/patchoptiondlg.cpp115
-rw-r--r--cervisia/patchoptiondlg.h60
-rw-r--r--cervisia/pics/Makefile.am1
-rw-r--r--cervisia/pics/README9
-rw-r--r--cervisia/pics/cr16-action-vcs_add.pngbin0 -> 904 bytes
-rw-r--r--cervisia/pics/cr16-action-vcs_commit.pngbin0 -> 908 bytes
-rw-r--r--cervisia/pics/cr16-action-vcs_diff.pngbin0 -> 950 bytes
-rw-r--r--cervisia/pics/cr16-action-vcs_remove.pngbin0 -> 812 bytes
-rw-r--r--cervisia/pics/cr16-action-vcs_status.pngbin0 -> 942 bytes
-rw-r--r--cervisia/pics/cr16-action-vcs_update.pngbin0 -> 869 bytes
-rw-r--r--cervisia/pics/cr22-action-vcs_add.pngbin0 -> 1337 bytes
-rw-r--r--cervisia/pics/cr22-action-vcs_commit.pngbin0 -> 1355 bytes
-rw-r--r--cervisia/pics/cr22-action-vcs_diff.pngbin0 -> 1576 bytes
-rw-r--r--cervisia/pics/cr22-action-vcs_remove.pngbin0 -> 1186 bytes
-rw-r--r--cervisia/pics/cr22-action-vcs_status.pngbin0 -> 1443 bytes
-rw-r--r--cervisia/pics/cr22-action-vcs_update.pngbin0 -> 1277 bytes
-rw-r--r--cervisia/pics/cr32-action-vcs_add.pngbin0 -> 2135 bytes
-rw-r--r--cervisia/pics/cr32-action-vcs_commit.pngbin0 -> 2172 bytes
-rw-r--r--cervisia/pics/cr32-action-vcs_diff.pngbin0 -> 2833 bytes
-rw-r--r--cervisia/pics/cr32-action-vcs_remove.pngbin0 -> 1901 bytes
-rw-r--r--cervisia/pics/cr32-action-vcs_status.pngbin0 -> 2400 bytes
-rw-r--r--cervisia/pics/cr32-action-vcs_update.pngbin0 -> 2046 bytes
-rw-r--r--cervisia/pics/cr48-action-vcs_add.pngbin0 -> 3617 bytes
-rw-r--r--cervisia/pics/cr48-action-vcs_commit.pngbin0 -> 3712 bytes
-rw-r--r--cervisia/pics/cr48-action-vcs_diff.pngbin0 -> 5241 bytes
-rw-r--r--cervisia/pics/cr48-action-vcs_remove.pngbin0 -> 3212 bytes
-rw-r--r--cervisia/pics/cr48-action-vcs_status.pngbin0 -> 4265 bytes
-rw-r--r--cervisia/pics/cr48-action-vcs_update.pngbin0 -> 3554 bytes
-rw-r--r--cervisia/pics/crsc-action-vcs_add.svgzbin0 -> 6021 bytes
-rw-r--r--cervisia/pics/crsc-action-vcs_commit.svgzbin0 -> 6954 bytes
-rw-r--r--cervisia/pics/crsc-action-vcs_diff.svgzbin0 -> 6562 bytes
-rw-r--r--cervisia/pics/crsc-action-vcs_remove.svgzbin0 -> 5759 bytes
-rw-r--r--cervisia/pics/crsc-action-vcs_status.svgzbin0 -> 7371 bytes
-rw-r--r--cervisia/pics/crsc-action-vcs_update.svgzbin0 -> 6887 bytes
-rw-r--r--cervisia/progressdlg.cpp276
-rw-r--r--cervisia/progressdlg.h68
-rw-r--r--cervisia/protocolview.cpp196
-rw-r--r--cervisia/protocolview.h78
-rw-r--r--cervisia/qttableview.cpp2278
-rw-r--r--cervisia/qttableview.h252
-rw-r--r--cervisia/repositories.cpp129
-rw-r--r--cervisia/repositories.h38
-rw-r--r--cervisia/repositorydlg.cpp504
-rw-r--r--cervisia/repositorydlg.h77
-rw-r--r--cervisia/resolvedlg.cpp634
-rw-r--r--cervisia/resolvedlg.h98
-rw-r--r--cervisia/resolvedlg_p.cpp59
-rw-r--r--cervisia/resolvedlg_p.h51
-rw-r--r--cervisia/settingsdlg.cpp385
-rw-r--r--cervisia/settingsdlg.h104
-rw-r--r--cervisia/settingsdlg_advanced.ui97
-rw-r--r--cervisia/stringmatcher.cpp146
-rw-r--r--cervisia/stringmatcher.h77
-rw-r--r--cervisia/tagdlg.cpp147
-rw-r--r--cervisia/tagdlg.h74
-rw-r--r--cervisia/tests/resolvedlg-BR74903-test-01.txt4
-rw-r--r--cervisia/tests/resolvedlg-conflict-test-01.txt5
-rw-r--r--cervisia/tests/resolvedlg-conflict-test-02.txt9
-rw-r--r--cervisia/tooltip.cpp111
-rw-r--r--cervisia/tooltip.h75
-rw-r--r--cervisia/updatedlg.cpp161
-rw-r--r--cervisia/updatedlg.h66
-rw-r--r--cervisia/updateview.cpp629
-rw-r--r--cervisia/updateview.h117
-rw-r--r--cervisia/updateview_items.cpp826
-rw-r--r--cervisia/updateview_items.h169
-rw-r--r--cervisia/updateview_visitors.cpp98
-rw-r--r--cervisia/updateview_visitors.h69
-rw-r--r--cervisia/version.h2
-rw-r--r--cervisia/watchdlg.cpp107
-rw-r--r--cervisia/watchdlg.h52
-rw-r--r--cervisia/watchersdlg.cpp121
-rw-r--r--cervisia/watchersdlg.h45
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>&amp;File</text>
+ <Merge/>
+ </Menu>
+ <Menu name="settings"><text>&amp;Settings</text>
+ <DefineGroup name="configure_merge_group" append="configure_merge" />
+ <DefineGroup name="save_merge_group" append="show_merge" />
+ </Menu>
+ <Menu name="help"><text>&amp;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>&amp;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>&amp;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>&amp;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>&amp;Repository</text>
+ <Action name="repository_create"/>
+ <Action name="repository_checkout"/>
+ <Action name="repository_import"/>
+ <Separator/>
+ <Action name="show_repositories"/>
+ </Menu>
+ <Menu name="settings"><text>&amp;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>&amp;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
new file mode 100644
index 00000000..3850214d
--- /dev/null
+++ b/cervisia/hi16-app-cervisia.png
Binary files differ
diff --git a/cervisia/hi22-app-cervisia.png b/cervisia/hi22-app-cervisia.png
new file mode 100644
index 00000000..253ec4a0
--- /dev/null
+++ b/cervisia/hi22-app-cervisia.png
Binary files differ
diff --git a/cervisia/hi32-app-cervisia.png b/cervisia/hi32-app-cervisia.png
new file mode 100644
index 00000000..1eda7cb8
--- /dev/null
+++ b/cervisia/hi32-app-cervisia.png
Binary files differ
diff --git a/cervisia/hi48-app-cervisia.png b/cervisia/hi48-app-cervisia.png
new file mode 100644
index 00000000..309cc49f
--- /dev/null
+++ b/cervisia/hi48-app-cervisia.png
Binary files differ
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>&nbsp;&nbsp;");
+ text += QStyleSheet::escape(m_author);
+ text += QString::fromLatin1("&nbsp;&nbsp;<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(&para, &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(&paraFrom, &indexFrom, &paraTo, &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(&para, &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(&para, &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 += " &nbsp;[<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
new file mode 100644
index 00000000..43c13620
--- /dev/null
+++ b/cervisia/pics/cr16-action-vcs_add.png
Binary files differ
diff --git a/cervisia/pics/cr16-action-vcs_commit.png b/cervisia/pics/cr16-action-vcs_commit.png
new file mode 100644
index 00000000..71f6b3dc
--- /dev/null
+++ b/cervisia/pics/cr16-action-vcs_commit.png
Binary files differ
diff --git a/cervisia/pics/cr16-action-vcs_diff.png b/cervisia/pics/cr16-action-vcs_diff.png
new file mode 100644
index 00000000..9cc127ee
--- /dev/null
+++ b/cervisia/pics/cr16-action-vcs_diff.png
Binary files differ
diff --git a/cervisia/pics/cr16-action-vcs_remove.png b/cervisia/pics/cr16-action-vcs_remove.png
new file mode 100644
index 00000000..8d4a9e72
--- /dev/null
+++ b/cervisia/pics/cr16-action-vcs_remove.png
Binary files differ
diff --git a/cervisia/pics/cr16-action-vcs_status.png b/cervisia/pics/cr16-action-vcs_status.png
new file mode 100644
index 00000000..186dcd70
--- /dev/null
+++ b/cervisia/pics/cr16-action-vcs_status.png
Binary files differ
diff --git a/cervisia/pics/cr16-action-vcs_update.png b/cervisia/pics/cr16-action-vcs_update.png
new file mode 100644
index 00000000..c3ba632c
--- /dev/null
+++ b/cervisia/pics/cr16-action-vcs_update.png
Binary files differ
diff --git a/cervisia/pics/cr22-action-vcs_add.png b/cervisia/pics/cr22-action-vcs_add.png
new file mode 100644
index 00000000..a083f822
--- /dev/null
+++ b/cervisia/pics/cr22-action-vcs_add.png
Binary files differ
diff --git a/cervisia/pics/cr22-action-vcs_commit.png b/cervisia/pics/cr22-action-vcs_commit.png
new file mode 100644
index 00000000..c0c96ef6
--- /dev/null
+++ b/cervisia/pics/cr22-action-vcs_commit.png
Binary files differ
diff --git a/cervisia/pics/cr22-action-vcs_diff.png b/cervisia/pics/cr22-action-vcs_diff.png
new file mode 100644
index 00000000..5c5d444b
--- /dev/null
+++ b/cervisia/pics/cr22-action-vcs_diff.png
Binary files differ
diff --git a/cervisia/pics/cr22-action-vcs_remove.png b/cervisia/pics/cr22-action-vcs_remove.png
new file mode 100644
index 00000000..b08c25e3
--- /dev/null
+++ b/cervisia/pics/cr22-action-vcs_remove.png
Binary files differ
diff --git a/cervisia/pics/cr22-action-vcs_status.png b/cervisia/pics/cr22-action-vcs_status.png
new file mode 100644
index 00000000..974fbb65
--- /dev/null
+++ b/cervisia/pics/cr22-action-vcs_status.png
Binary files differ
diff --git a/cervisia/pics/cr22-action-vcs_update.png b/cervisia/pics/cr22-action-vcs_update.png
new file mode 100644
index 00000000..5be225a7
--- /dev/null
+++ b/cervisia/pics/cr22-action-vcs_update.png
Binary files differ
diff --git a/cervisia/pics/cr32-action-vcs_add.png b/cervisia/pics/cr32-action-vcs_add.png
new file mode 100644
index 00000000..5d4ae629
--- /dev/null
+++ b/cervisia/pics/cr32-action-vcs_add.png
Binary files differ
diff --git a/cervisia/pics/cr32-action-vcs_commit.png b/cervisia/pics/cr32-action-vcs_commit.png
new file mode 100644
index 00000000..ae10315e
--- /dev/null
+++ b/cervisia/pics/cr32-action-vcs_commit.png
Binary files differ
diff --git a/cervisia/pics/cr32-action-vcs_diff.png b/cervisia/pics/cr32-action-vcs_diff.png
new file mode 100644
index 00000000..ee490e08
--- /dev/null
+++ b/cervisia/pics/cr32-action-vcs_diff.png
Binary files differ
diff --git a/cervisia/pics/cr32-action-vcs_remove.png b/cervisia/pics/cr32-action-vcs_remove.png
new file mode 100644
index 00000000..a4ef8905
--- /dev/null
+++ b/cervisia/pics/cr32-action-vcs_remove.png
Binary files differ
diff --git a/cervisia/pics/cr32-action-vcs_status.png b/cervisia/pics/cr32-action-vcs_status.png
new file mode 100644
index 00000000..804f5397
--- /dev/null
+++ b/cervisia/pics/cr32-action-vcs_status.png
Binary files differ
diff --git a/cervisia/pics/cr32-action-vcs_update.png b/cervisia/pics/cr32-action-vcs_update.png
new file mode 100644
index 00000000..13124fe5
--- /dev/null
+++ b/cervisia/pics/cr32-action-vcs_update.png
Binary files differ
diff --git a/cervisia/pics/cr48-action-vcs_add.png b/cervisia/pics/cr48-action-vcs_add.png
new file mode 100644
index 00000000..c0a8dee8
--- /dev/null
+++ b/cervisia/pics/cr48-action-vcs_add.png
Binary files differ
diff --git a/cervisia/pics/cr48-action-vcs_commit.png b/cervisia/pics/cr48-action-vcs_commit.png
new file mode 100644
index 00000000..aacc2b7e
--- /dev/null
+++ b/cervisia/pics/cr48-action-vcs_commit.png
Binary files differ
diff --git a/cervisia/pics/cr48-action-vcs_diff.png b/cervisia/pics/cr48-action-vcs_diff.png
new file mode 100644
index 00000000..5a954ee6
--- /dev/null
+++ b/cervisia/pics/cr48-action-vcs_diff.png
Binary files differ
diff --git a/cervisia/pics/cr48-action-vcs_remove.png b/cervisia/pics/cr48-action-vcs_remove.png
new file mode 100644
index 00000000..caa2fcc4
--- /dev/null
+++ b/cervisia/pics/cr48-action-vcs_remove.png
Binary files differ
diff --git a/cervisia/pics/cr48-action-vcs_status.png b/cervisia/pics/cr48-action-vcs_status.png
new file mode 100644
index 00000000..a98d15dc
--- /dev/null
+++ b/cervisia/pics/cr48-action-vcs_status.png
Binary files differ
diff --git a/cervisia/pics/cr48-action-vcs_update.png b/cervisia/pics/cr48-action-vcs_update.png
new file mode 100644
index 00000000..ab7b2d89
--- /dev/null
+++ b/cervisia/pics/cr48-action-vcs_update.png
Binary files differ
diff --git a/cervisia/pics/crsc-action-vcs_add.svgz b/cervisia/pics/crsc-action-vcs_add.svgz
new file mode 100644
index 00000000..b6e9fc62
--- /dev/null
+++ b/cervisia/pics/crsc-action-vcs_add.svgz
Binary files differ
diff --git a/cervisia/pics/crsc-action-vcs_commit.svgz b/cervisia/pics/crsc-action-vcs_commit.svgz
new file mode 100644
index 00000000..b1d63c7b
--- /dev/null
+++ b/cervisia/pics/crsc-action-vcs_commit.svgz
Binary files differ
diff --git a/cervisia/pics/crsc-action-vcs_diff.svgz b/cervisia/pics/crsc-action-vcs_diff.svgz
new file mode 100644
index 00000000..45549f69
--- /dev/null
+++ b/cervisia/pics/crsc-action-vcs_diff.svgz
Binary files differ
diff --git a/cervisia/pics/crsc-action-vcs_remove.svgz b/cervisia/pics/crsc-action-vcs_remove.svgz
new file mode 100644
index 00000000..b6165bb7
--- /dev/null
+++ b/cervisia/pics/crsc-action-vcs_remove.svgz
Binary files differ
diff --git a/cervisia/pics/crsc-action-vcs_status.svgz b/cervisia/pics/crsc-action-vcs_status.svgz
new file mode 100644
index 00000000..32b1ed9d
--- /dev/null
+++ b/cervisia/pics/crsc-action-vcs_status.svgz
Binary files differ
diff --git a/cervisia/pics/crsc-action-vcs_update.svgz b/cervisia/pics/crsc-action-vcs_update.svgz
new file mode 100644
index 00000000..b251d05f
--- /dev/null
+++ b/cervisia/pics/crsc-action-vcs_update.svgz
Binary files differ
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>&amp;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 &amp;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